From bf09704a9eb19a6dfe5d52625dca8602dec7279a Mon Sep 17 00:00:00 2001 From: Sergey Semashko Date: Wed, 22 Apr 2020 22:40:54 -0400 Subject: [PATCH 01/60] Add scroll view to import screen (#640) --- app/views/Import.js | 6 +- .../__snapshots__/Import.spec.js.snap | 247 +++++++++--------- 2 files changed, 129 insertions(+), 124 deletions(-) diff --git a/app/views/Import.js b/app/views/Import.js index 9b63c15ca0..2cd93434ca 100644 --- a/app/views/Import.js +++ b/app/views/Import.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Dimensions, Linking, + ScrollView, StyleSheet, TouchableOpacity, View, @@ -65,7 +66,7 @@ const ImportScreen = props => { - + {languages.t('label.import_step_1')} @@ -107,7 +108,7 @@ const ImportScreen = props => { ) : null} - + ); }; @@ -125,6 +126,7 @@ const styles = StyleSheet.create({ fontWeight: 'bold', fontSize: 22, padding: 5, + paddingBottom: 20, }, main: { flex: 1, diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index 62e650fb7a..a2949dee4a 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -93,7 +93,7 @@ Array [ Import Locations - - - - Adding location data from Google will give you a head start on building your recent locations. - - - Before you can import, you must first 'Take out' your location data from Google. - - - Visit Google Takeout and export your Location History using following settings: -1. Delivery method: "Add to Drive" -2. Frequency: "Export once" -3. File type & size: ".zip" and "1GB" -4. Google sends an email when the export is ready -5. Return here to import locations. Import options: -- Import from Google Drive -- Download from browser, then import from local phone files. Make sure to be on WiFi network as files can be big. - + - VISIT GOOGLE TAKEOUT + Adding location data from Google will give you a head start on building your recent locations. - - + use="body1" + > + Before you can import, you must first 'Take out' your location data from Google. + - IMPORT LOCATIONS + Visit Google Takeout and export your Location History using following settings: +1. Delivery method: "Add to Drive" +2. Frequency: "Export once" +3. File type & size: ".zip" and "1GB" +4. Google sends an email when the export is ready +5. Return here to import locations. Import options: +- Import from Google Drive +- Download from browser, then import from local phone files. Make sure to be on WiFi network as files can be big. + + + VISIT GOOGLE TAKEOUT + + + + + IMPORT LOCATIONS + + - + , ] `; From 73a33d13c6b616e186dfbec4b21e6384b63930c5 Mon Sep 17 00:00:00 2001 From: Krishna <503055+Krish023@users.noreply.github.com> Date: Thu, 23 Apr 2020 11:45:29 +0530 Subject: [PATCH 02/60] Fix missing settings icon on iOS 12.0.x (#643) --- app/assets/svgs/backArrow.js | 2 +- app/assets/svgs/settingsGear.js | 2 +- app/views/__tests__/__snapshots__/Import.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/Licenses.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/News.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/Settings.spec.js.snap | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/svgs/backArrow.js b/app/assets/svgs/backArrow.js index 5bd6f593d3..9f2398ab2a 100644 --- a/app/assets/svgs/backArrow.js +++ b/app/assets/svgs/backArrow.js @@ -1,6 +1,6 @@ export default ` - + diff --git a/app/assets/svgs/settingsGear.js b/app/assets/svgs/settingsGear.js index ccf3f275f4..d1d3bc2de4 100644 --- a/app/assets/svgs/settingsGear.js +++ b/app/assets/svgs/settingsGear.js @@ -1,5 +1,5 @@ export default ` - + diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index a2949dee4a..594dcd2ac1 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -65,7 +65,7 @@ Array [ } xml=" - + diff --git a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap index e90131ea07..8edaaf250e 100644 --- a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap @@ -66,7 +66,7 @@ exports[`renders correctly 1`] = ` } xml=" - + diff --git a/app/views/__tests__/__snapshots__/News.spec.js.snap b/app/views/__tests__/__snapshots__/News.spec.js.snap index 5acbd573d2..8112bf9494 100644 --- a/app/views/__tests__/__snapshots__/News.spec.js.snap +++ b/app/views/__tests__/__snapshots__/News.spec.js.snap @@ -93,7 +93,7 @@ exports[`renders correctly 1`] = ` } xml=" - + diff --git a/app/views/__tests__/__snapshots__/Settings.spec.js.snap b/app/views/__tests__/__snapshots__/Settings.spec.js.snap index d013203fbc..492fea34a9 100644 --- a/app/views/__tests__/__snapshots__/Settings.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Settings.spec.js.snap @@ -66,7 +66,7 @@ exports[`renders correctly 1`] = ` } xml=" - + From 8dd4cbd783497162b4cdab435f5623e11dae1e3c Mon Sep 17 00:00:00 2001 From: NethravathiPuttaraju <62654508+NethravathiPuttraju@users.noreply.github.com> Date: Thu, 23 Apr 2020 20:09:52 +0530 Subject: [PATCH 03/60] Fix white background on choose provider screen (#624) --- app/views/ChooseProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index 4380ca4e0e..6280cda516 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -398,7 +398,6 @@ const styles = StyleSheet.create({ padding: 20, width: '96%', alignSelf: 'center', - backgroundColor: colors.WHITE, }, row: { flex: 1, From 363864a196c38a727a17b0a892648e7883794757 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 23 Apr 2020 15:05:43 -0700 Subject: [PATCH 04/60] Revert "Fix missing settings icon on iOS 12.0.x (#643)" (#650) This reverts commit 73a33d13c6b616e186dfbec4b21e6384b63930c5. --- app/assets/svgs/backArrow.js | 2 +- app/assets/svgs/settingsGear.js | 2 +- app/views/__tests__/__snapshots__/Import.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/Licenses.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/News.spec.js.snap | 2 +- app/views/__tests__/__snapshots__/Settings.spec.js.snap | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/svgs/backArrow.js b/app/assets/svgs/backArrow.js index 9f2398ab2a..5bd6f593d3 100644 --- a/app/assets/svgs/backArrow.js +++ b/app/assets/svgs/backArrow.js @@ -1,6 +1,6 @@ export default ` - + diff --git a/app/assets/svgs/settingsGear.js b/app/assets/svgs/settingsGear.js index d1d3bc2de4..ccf3f275f4 100644 --- a/app/assets/svgs/settingsGear.js +++ b/app/assets/svgs/settingsGear.js @@ -1,5 +1,5 @@ export default ` - + diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index 594dcd2ac1..a2949dee4a 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -65,7 +65,7 @@ Array [ } xml=" - + diff --git a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap index 8edaaf250e..e90131ea07 100644 --- a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap @@ -66,7 +66,7 @@ exports[`renders correctly 1`] = ` } xml=" - + diff --git a/app/views/__tests__/__snapshots__/News.spec.js.snap b/app/views/__tests__/__snapshots__/News.spec.js.snap index 8112bf9494..5acbd573d2 100644 --- a/app/views/__tests__/__snapshots__/News.spec.js.snap +++ b/app/views/__tests__/__snapshots__/News.spec.js.snap @@ -93,7 +93,7 @@ exports[`renders correctly 1`] = ` } xml=" - + diff --git a/app/views/__tests__/__snapshots__/Settings.spec.js.snap b/app/views/__tests__/__snapshots__/Settings.spec.js.snap index 492fea34a9..d013203fbc 100644 --- a/app/views/__tests__/__snapshots__/Settings.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Settings.spec.js.snap @@ -66,7 +66,7 @@ exports[`renders correctly 1`] = ` } xml=" - + From 72f16809895fbd697bc3ac6d27227d85e823d26c Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 23 Apr 2020 15:30:45 -0700 Subject: [PATCH 05/60] Disable google import on release builds (#651) --- app/views/Settings.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/Settings.js b/app/views/Settings.js index cf3cd56c2f..77f86cfbaa 100644 --- a/app/views/Settings.js +++ b/app/views/Settings.js @@ -128,9 +128,11 @@ export const SettingsScreen = ({ navigation }) => { /> -
- -
+ {__DEV__ && ( +
+ +
+ )}
Date: Thu, 23 Apr 2020 16:19:40 -0700 Subject: [PATCH 06/60] Fix settings locale override storage (#649) --- app/locales/languages.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/locales/languages.js b/app/locales/languages.js index 26161119f8..9cc8384977 100644 --- a/app/locales/languages.js +++ b/app/locales/languages.js @@ -66,9 +66,6 @@ export function useLanguageDirection() { export async function setUserLocaleOverride(locale) { await setLocale(locale); - if (locale === supportedDeviceLanguageOrEnglish()) { - locale = undefined; - } await SetStoreData(LANG_OVERRIDE, locale); } From 3a1af3cba9b36810e760db10e85f058d3ad381e0 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 23 Apr 2020 16:30:12 -0700 Subject: [PATCH 07/60] Suggest screenshots and fixed issues in PR template (#637) --- .github/PULL_REQUEST_TEMPLATE.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7e31cf5742..6107a0b784 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,15 @@ -## Description + - + -## How to test +#### Linked issues: + + + +#### Screenshots: + + + +#### How to test From b066279aeb5f69b657a4573855b8a8fbf80d63a7 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 23 Apr 2020 17:04:29 -0700 Subject: [PATCH 08/60] Temporarily filter out extra languages in release mode (#654) --- app/locales/languages.js | 41 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/app/locales/languages.js b/app/locales/languages.js index 9cc8384977..dd39274ac1 100644 --- a/app/locales/languages.js +++ b/app/locales/languages.js @@ -69,6 +69,25 @@ export async function setUserLocaleOverride(locale) { await SetStoreData(LANG_OVERRIDE, locale); } +/** Languages only available in dev builds. */ +const DEV_LANGUAGES = __DEV__ + ? { + ar: { label: 'العربية', translation: ar }, + es: { label: 'Español', translation: es }, + fr: { label: 'Français', translation: fr }, + id: { label: 'Indonesia', translation: id }, + it: { label: 'Italiano', translation: it }, + ml: { label: 'മലയാളം', translation: ml }, + nl: { label: 'Nederlands', translation: nl }, + pl: { label: 'Polski', translation: pl }, + ro: { label: 'Română', translation: ro }, + ru: { label: 'Русский', translation: ru }, + sk: { label: 'Slovak', translation: sk }, + vi: { label: 'Vietnamese', translation: vi }, + zh_Hant: { label: '繁體中文', translation: zh_Hant }, + } + : {}; + i18next.use(initReactI18next).init({ interpolation: { // React already does escaping @@ -78,31 +97,19 @@ i18next.use(initReactI18next).init({ fallbackLng: 'en', // If language detector fails returnEmptyString: false, resources: { - ar: { label: 'العربية', translation: ar }, en: { label: 'English', translation: en }, - es: { label: 'Español', translation: es }, - fr: { label: 'Français', translation: fr }, ht: { label: 'Kreyòl ayisyen', translation: ht }, - id: { label: 'Indonesia', translation: id }, - it: { label: 'Italiano', translation: it }, - ml: { label: 'മലയാളം', translation: ml }, - nl: { label: 'Nederlands', translation: nl }, - pl: { label: 'Polski', translation: pl }, - ro: { label: 'Română', translation: ro }, - ru: { label: 'Русский', translation: ru }, - sk: { label: 'Slovak', translation: sk }, - vi: { label: 'Vietnamese', translation: vi }, - zh_Hant: { label: '繁體中文', translation: zh_Hant }, + ...DEV_LANGUAGES, }, }); /** The known locale list */ -export const LOCALE_LIST = Object.entries(i18next.options.resources).map( - ([langCode, lang]) => ({ +export const LOCALE_LIST = Object.entries(i18next.options.resources) + .map(([langCode, lang]) => ({ value: langCode, label: lang.label, - }), -); + })) + .sort((a, b) => a.value > b.value); /** A map of locale code to name. */ export const LOCALE_NAME = Object.entries(i18next.options.resources).reduce( From b9ea39667c4b19ba871f06756b765a1c8d0d3d36 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 23 Apr 2020 17:05:54 -0700 Subject: [PATCH 09/60] Lokalise: Translations update (ht) (#655) * Lokalise: updates * Fix up Lokalise discrepancy manually --- app/locales/ht.json | 77 ++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/app/locales/ht.json b/app/locales/ht.json index 89386a70c8..31928b4b98 100644 --- a/app/locales/ht.json +++ b/app/locales/ht.json @@ -1,9 +1,19 @@ { + "history": { + "no_exposure": "Pa gen kontak", + "possible_exposure": "Posib", + "possible_exposure_para": "Li posib ke ou te an kontak oswa pre yon moun ki teste pozitif pou Kowonaviris", + "timeline": "Kalandriye", + "what_does_this_mean": "Ki sa sa a vle di?", + "what_does_this_mean_para": "Daprè lis done GPS ou, li posib ke ou te an kontak oswa tou pre yon moun ki te dyagnostike ak Kowonaviris. Sa pa vle di ou enfekte men se yon posiblite. \n\n Pou plis enfòmasyon sou sa ou ta dwe fè ou ka al gade sou sit entènèt Mayo Klinik la.", + "what_if_no_symptoms": "E si mwen pa genyen sentòm yo?", + "what_if_no_symptoms_para": "Si ou pa gen okenn sentòm men ou ta renmen fè tès la, ou ka ale nan lokal tès ki pi pre w la. \n\n Moun ki pa montre sentòm yo ka pafwa toujou gen enfeksyon an e enfekte lòt moun. Lèw pran prekosyon nan sa ki gen pou wè ak distans sosyal ak antre an kontak ak gwoup anoil moun oswa moun ki gen risk (granmoun aje a, moun ki gen lòt pwoblèm sante) enpòtan nan jere risk pou tèt pa w ak risk pou lòt moun." + }, "label": { "about_title": "Apropo", "authorities_add_button_label": "Ajoute yon sous ou fè konfyans", "authorities_add_url": "Ajoute otorite a pati yon URL", - "authorities_desc": "Chwazi otorite swen sante ou fè konfyans nan zòn ou an pou jwenn done ekspoze yo. Swa ou chwazi yon non ki soti nan rejis global la, oswa antre non adrès entènèt yon otorite, ki te aplike Chemen Sekirize, te bay.", + "authorities_desc": "Chwazi otorite ke ou fè konfyans nan zòn ou an pou jwenn done ekspoze yo. Swa ou chwazi yon non ki soti nan rejis global la, oswa antre non adrès entènèt yon otorite, ki te aplike Chase Kowona (\"Safe Paths\"), te bay.", "authorities_input_placeholder": "Kole URL ou an la", "authorities_no_sources": "Ou poko genyen sous done!", "authorities_removal_alert_cancel": "Anile", @@ -11,25 +21,34 @@ "authorities_removal_alert_proceed": "Kontinye", "authorities_removal_alert_title": "Retire otorite", "authorities_title": "Sous ou ka fè konfyans", - "choose_provider_subtitle": "Pou kapab toujou rete enfòme a ekspozisyon yo, ou bezwen enskri nan yon otorite sante.", - "choose_provider_title": "Chwazi otorite sante", + "choose_provider_subtitle": "Pou kapab toujou rete enfòme a ekspozisyon yo, ou bezwen enskri nan yon otorite.", + "choose_provider_title": "Chwazi otorite a", "commitment": "Angajman", - "commitment_para": "Chemen Sekirize byen anrejistre e tckeke entèraksyon ak moun ki itilize posisyon ou. Done ou yo pap JANM kite telefòn ou san konsantman ou.", - "event_history_subtitle": "Konprann ekspozisyo pèsonèl ou daprè enfòmasyon Otorite Santew lan pataje.", + "commitment_para": "Chase Kowona (\"Safe Paths\") byen anrejistre e tckeke entèraksyon ak moun ki itilize posisyon ou. Done ou yo pap JANM kite telefòn ou san konsantman ou.", + "default_news_site_name": "Nouvèl sou Chase Kowona (\"Safe Paths\")", + "event_history_subtitle": "Konprann ekspozisyon pèsonèl ou daprè enfòmasyon Otorite a pataje.", "event_history_title": "Lis ekspozisyon", - "export_para_1": "Si ou teste pozitif pou COVID-19, tanpri fè pati pa ou nan pataje list pozisyon ou ak otorite lokal yo.", - "export_para_2": "Lokalizasyon yo prale nan fòm yon senp lis lè ak kowòdone zòn yo, okenn enfòmasyon anplis pap transfere", + "export_para_1": "Si ou teste pozitif pou Kowonaviris, tanpri fè pati pa ou a nan pataje lis lokalizasyon ou ak otorite lokal yo.", + "export_para_2": "Lokalizasyon ou yo ap pataje sou fòm yon senp lis lè ak zòn, pap gen okenn enfòmasyon anplis ki prale.", "home_at_risk_header": "Ou ka gen risk", - "home_at_risk_subtext": "Daprè done disponib yo, ou te kwaze chemen ak yon moun ki te teste pozitif pou COVID-19.", + "home_at_risk_subsubtext": "Sa pa vle di ou enfekte.", + "home_at_risk_subtext": "Daprè done disponib yo, ou te kwaze chemen ak yon moun ki te teste pozitif pou Kowonaviris.", "home_enable_location": "Aktive done lokalizasyon", + "home_mayo_link_heading": "Plis enfòmasyon sou Kowonaviris", + "home_mayo_link_label": "ki soti nan Mayo Clinic", "home_no_contact_header": "Pa gen kontak", - "home_no_contact_subtext": "Daprè done disponib yo, ou pat prè pèsonn moun ki te teste pozitif pou COVID-19.", + "home_no_contact_subtext": "Daprè done disponib yo, ou pat prè pèsonn moun ki te teste pozitif pou Kowonaviris.", + "home_setting_off_header": "Pa gen kontak", + "home_setting_off_subtext": "Nou pa ka di si w gen risk sof si ou aktive lis lokalizasyon nan tablodbò a", "home_unknown_header": "Enkoni", "home_unknown_subtext": "Nou pa ka di si ou nan risk sof si ou pèmèt app la gen aksè a lokalizasyon ou.", - "import_step_1": "1. Konekte sou kont Gmail ou e telechaje lis pozisyon ou yo", - "import_step_2": "2. Lèl fin telechaje, relouvri paj sa. Done yo ap monte otomatikman.", + "import_step": "{\"one\": \"Ajoute done lokalizasyon ki sòti sou Google ap ba ou komansman yon ide sou dènye lokalizasyon ou yo.\", \"other\": \"Anvan ou ka enpòte, ou dwe 'Pran' done lokalizasyon ou yo sou Google avan.\"}", + "import_takeout": "Vizite Google Takeout", "import_title": "Pran done lokalizasyon", - "latest_news": "Dènye nouvèl", + "language_change_alert_cancel": "Anile", + "language_change_alert_proced": "Rekòmanse", + "language_change_alert_title": "Aplikasyon an bezwen rekòmanse dèyè nèt pou itiize lang sa ", + "latest_news": "Dènye nouvèl ", "launch_done_header": "Fini nèt", "launch_done_subheader": "Ou pare pouw woule. Sonje byen, ou ka toujou fè mizajou sou preferans ou yo pita.", "launch_enable_location": "Aktive lokalizasyon", @@ -44,7 +63,7 @@ "launch_notif_subheader": "Nou pa pral deranje ou eksepte pou nou pataje enfòmasyon siw gen risk sou yon potansyèl ekspozisyon.", "launch_notification_access": "Pèmèt notifikasyon", "launch_screen1_header": "Wout pou tounen nan nòmal kòmanse isit la.", - "launch_screen2_header": "Resevwa notifikasyon si ou kwaze wout yon moun ki diagnostike COVID-19 aprè.", + "launch_screen2_header": "Resevwa notifikasyon si ou kwaze wout yon moun ki diagnostike Kowonaviris aprè.", "launch_screen2_subheader": "Konesans se pouvwa", "launch_screen3_header": "Si ou teste pozitif, ou ka pataje done pouw ede lòt moun pandan wap kenbe idantite ou sekrè.", "launch_screen3_subheader": "Èd sa yo ede kenbe tout kominote a an sekirite.", @@ -54,38 +73,44 @@ "legal_page_title": "Legal", "less_than_one_minute": "Mwens pase 1 minit", "loading_public_data": "Chajman done...", - "location_disabled_message": "COVID Chemen Sekirize mande sèvis lokalizasyon", + "location_disabled_message": "Chase Kowona (\"Safe Paths\") mande sèvis lokalizasyon", "location_disabled_title": "GPS ou te dezaktive", - "location_enabled_message": "COVID Chemen Sekirize ap byen estoke kowòdone GPS ou yon fwa chak senk minit sou aparèy sa a.", - "location_enabled_title": "COVID Chemen Sekirize active", + "location_enabled_message": "Chase Kowona (\"Safe Paths\") ap byen estoke kowòdone GPS ou yon fwa chak senk minit sou aparèy sa a.", + "location_enabled_title": "Chase Kowona (\"Safe Paths\") active", + "logging_active": "Lokalizasyon aktif", + "logging_inactive": "Lokalizasyon inaktif", "maps_import_button_text": "Voye lokalizasyon ki pase yo", - "maps_import_disclaimer": "Chemen san danje pa gen okenn afilyasyon ak Google epi li pa janm pataje done ou yo.", - "maps_import_text": "Pou wè si ou rankontre yon moun ki gen COVID-19, telechaje aplikasyon sa a avan, ou kapab voye lis localizasyon pèsonèl ou.", + "maps_import_disclaimer": "Chase Kowona (\"Safe Paths\") pa gen okenn afilyasyon ak Google epi li pa janm pataje done ou yo.", + "maps_import_text": "Pou wè si ou rankontre yon moun ki gen Kowonaviris, telechaje aplikasyon sa a avan, ou kapab voye lis localizasyon pèsonèl ou.", "maps_import_title": "Kat Google", "nCoV2019_url_info": "Pou plis enfòmasyon sou done pou kat sa a", - "news_subtitle": "Li dènye mizajou COVID ki soti nan Otorite Santew e an jeneral ", + "news_subtitle": "Li dènye mizajou sou Kowonaviris ak videyo, atik, ilistrasyon ak lòt resous ke ou ka itilize pou jere sitiyasyon an pi byen. ", "news_title": "Dènye nouvèl", "no_data": "Pa gen okenn done", "notification_2_weeks_ago": "2 semenn pase", "notification_data_not_available": "Pa gen done ekspozisyon ki disponib.", + "notification_select_authority": "Chwazi Otorite a", "notification_title": "Ekspozisyon Pwofil pou 2 semèn", "notification_today": "Jodi a", - "notification_warning_text": "Si yon Otorite Swen Sante egziste nan zòn ou an ou kapab pran abònman pou jwenn dènye enfòmasyon si gen risk ekspozisyon", + "notification_warning_text": "Si yon Otorite egziste nan zòn ou an ou kapab pran abònman pou jwenn dènye enfòmasyon si gen risk ekspozisyon", "notifications_exposure_format": "{{daysAgo}} jou pase, ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", "notifications_exposure_format_today": "Jodi a ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", "notifications_exposure_format_yesterday": "Yè ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", - "overlap_found_button_label": "Done Piblik chaje", - "overlap_no_results_button_label": "Done Piblik chaje", - "overlap_para_1": "Kote vèt la reprezante lis kote ou ye a\n\nNan ti sèk limyè koulè wouj yo ap reprezante done piblik yo", + "notifications_no_exposure": "Pa gen ekspozisyon a Kowonaviris ki enrejistre pandan de semèn ki sot pase yo.", + "overlap_found_button_label": "Done Piblik Chaje", + "overlap_no_results_button_label": "Done Piblik Chaje", + "overlap_para_1": "Pati vèt yo reprezante lis lokalizasyon ou\nWonn mov pal yo reprezante done piblik yo", "overlap_title": "Tcheke sipèpozisyton yo", - "push_at_risk_message": "Ou te kwaze chemen avèk yon moun ki enfekte COVID-19", + "push_at_risk_message": "Ou te kwaze chemen avèk yon moun ki enfekte Kowonaviris", "push_at_risk_title": "Ou ka gen risk", + "see_exposure_history": "Wè lis ekspozisyon", "settings_title": "Tablodbò", "share_location_data": "Pataje done lokalizasyon", - "show_overlap": "Klike sou yo wè done piblik yo", + "show_overlap": "Klike sou yo pou wè done piblik yo", "team": "Ekip", "team_para": "Ekip nou an konpoze de yon group ki genyen ladanl epidemyolojis, enjenyè, syantis done, evanjeliS vi prive dijital, pwofesè ak chèchè nan anpil gran enstitisyon nan mond lan, tankou: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young ak Link Ventures.", - "tested_positive_subtitle": "Done prive ou kapab transfere a Otorite sante, konsève, oubyen pataje.", + "terms_of_use": "Règleman itilizasyon", + "tested_positive_subtitle": "Done prive ou kapab transfere a Otorite a, konsève, oubyen pataje.", "tested_positive_title": "Pataje lis lokalizasyon" } } From 933552c54cbdf981b9365af7f82fe483483b26ff Mon Sep 17 00:00:00 2001 From: denispapakul <47609057+denispapakul@users.noreply.github.com> Date: Fri, 24 Apr 2020 04:21:56 +0300 Subject: [PATCH 10/60] Fix languages specific for history page (#628) --- app/views/ExposureHistory/ExposureCalendarView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/ExposureHistory/ExposureCalendarView.js b/app/views/ExposureHistory/ExposureCalendarView.js index 96d6322e71..f18957cff7 100644 --- a/app/views/ExposureHistory/ExposureCalendarView.js +++ b/app/views/ExposureHistory/ExposureCalendarView.js @@ -73,6 +73,7 @@ const LegendRow = styled.View` flex-direction: row; justify-content: center; margin-top: 16px; + flex-wrap: wrap; `; const LegendItem = ({ children, riskLevel }) => { From e8a6431057a7a8a4558c61a1a7ae5e95f0e0ac0f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 23 Apr 2020 21:14:33 -0700 Subject: [PATCH 11/60] Add yaml file for version checking (#658) * Add yaml file for version checking * Fix typo --- versions.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 versions.yaml diff --git a/versions.yaml b/versions.yaml new file mode 100644 index 0000000000..8e79cf5de3 --- /dev/null +++ b/versions.yaml @@ -0,0 +1,4 @@ +# Versions less than specified will receive a gentle update notification (dismissable alert only) +latest version: "0.9.5" +# Versions less than specified will receive a mandatory update notification (push + non-dismissable alert) +mandatory update for less than: "0.9.4" From 69b73647ddc7e3eb4db987b2c13c6dfca964e5c4 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Fri, 24 Apr 2020 00:22:36 -0500 Subject: [PATCH 12/60] Add healthcare selection to onboarding flow (#657) Signed-off-by: Patrick Erichsen Co-authored-by: Mihail Lemeza Co-authored-by: Tim Stirrat --- app/Entry.js | 6 + app/views/onboarding/Onboarding1.js | 6 +- app/views/onboarding/Onboarding2.js | 11 +- app/views/onboarding/Onboarding3.js | 11 +- app/views/onboarding/Onboarding4.js | 11 +- app/views/onboarding/Onboarding5.js | 328 ++++++------------------- app/views/onboarding/Onboarding6.js | 360 ++++++++++++++++++++++++++++ e2e/Onboarding.spec.js | 5 + e2e/helpers/onboarding.js | 4 + e2e/pages/Onboarding5.po.js | 23 ++ e2e/pages/Onboarding6.po.js | 23 ++ 11 files changed, 508 insertions(+), 280 deletions(-) create mode 100644 app/views/onboarding/Onboarding6.js create mode 100644 e2e/pages/Onboarding5.po.js create mode 100644 e2e/pages/Onboarding6.po.js diff --git a/app/Entry.js b/app/Entry.js index 47d7372fe1..d2f9918bb2 100644 --- a/app/Entry.js +++ b/app/Entry.js @@ -19,6 +19,7 @@ import Onboarding2 from './views/onboarding/Onboarding2'; import Onboarding3 from './views/onboarding/Onboarding3'; import Onboarding4 from './views/onboarding/Onboarding4'; import Onboarding5 from './views/onboarding/Onboarding5'; +import Onboarding6 from './views/onboarding/Onboarding6'; import { SettingsScreen } from './views/Settings'; const Stack = createStackNavigator(); @@ -91,6 +92,11 @@ class Entry extends Component { component={Onboarding5} options={{ headerShown: false }} /> + - {label} + + {label} + )} diff --git a/app/views/onboarding/Onboarding2.js b/app/views/onboarding/Onboarding2.js index dd87152967..7e8543e05c 100644 --- a/app/views/onboarding/Onboarding2.js +++ b/app/views/onboarding/Onboarding2.js @@ -4,13 +4,12 @@ import { ImageBackground, StatusBar, StyleSheet, - Text, View, } from 'react-native'; import BackgroundImage from './../../assets/images/launchScreen2.png'; import ButtonWrapper from '../../components/ButtonWrapper'; -import { Typography } from '../../components/Typography'; +import { Type, Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; import fontFamily from '../../constants/fonts'; import languages from '../../locales/languages'; @@ -23,14 +22,14 @@ const Onboarding = props => { - + {languages.t('label.launch_screen2_header')} @@ -71,11 +70,7 @@ const styles = StyleSheet.create({ }, headerText: { color: Colors.VIOLET, - fontSize: 26, width: width * 0.8, - fontFamily: fontFamily.primaryMedium, - lineHeight: 26 * 1.1, - paddingTop: 26 * 1.1 - 26, }, subheaderText: { marginTop: '6%', diff --git a/app/views/onboarding/Onboarding3.js b/app/views/onboarding/Onboarding3.js index 8c5d92ae16..b6b6fbf681 100644 --- a/app/views/onboarding/Onboarding3.js +++ b/app/views/onboarding/Onboarding3.js @@ -4,13 +4,12 @@ import { ImageBackground, StatusBar, StyleSheet, - Text, View, } from 'react-native'; import BackgroundImage from './../../assets/images/launchScreen3.png'; import ButtonWrapper from '../../components/ButtonWrapper'; -import { Typography } from '../../components/Typography'; +import { Type, Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; import fontFamily from '../../constants/fonts'; import languages from '../../locales/languages'; @@ -23,14 +22,14 @@ const Onboarding = props => { - + {languages.t('label.launch_screen3_header')} @@ -71,11 +70,7 @@ const styles = StyleSheet.create({ }, headerText: { color: Colors.VIOLET, - fontSize: 26, width: width * 0.75, - fontFamily: fontFamily.primaryMedium, - lineHeight: 26 * 1.1, - paddingTop: 26 * 1.1 - 26, }, subheaderText: { marginTop: '6%', diff --git a/app/views/onboarding/Onboarding4.js b/app/views/onboarding/Onboarding4.js index 33a04df0dd..d910bf2681 100644 --- a/app/views/onboarding/Onboarding4.js +++ b/app/views/onboarding/Onboarding4.js @@ -4,13 +4,12 @@ import { ImageBackground, StatusBar, StyleSheet, - Text, View, } from 'react-native'; import BackgroundImage from './../../assets/images/launchScreen1.png'; import ButtonWrapper from '../../components/ButtonWrapper'; -import { Typography } from '../../components/Typography'; +import { Type, Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; import fontFamily from '../../constants/fonts'; import languages from '../../locales/languages'; @@ -23,14 +22,14 @@ const Onboarding = props => { - + {languages.t('label.launch_screen4_header')} @@ -71,11 +70,7 @@ const styles = StyleSheet.create({ }, headerText: { color: Colors.VIOLET, - fontSize: 26, width: width * 0.7, - fontFamily: fontFamily.primaryMedium, - lineHeight: 26 * 1.1, - paddingTop: 26 * 1.1 - 26, }, subheaderText: { marginTop: '6%', diff --git a/app/views/onboarding/Onboarding5.js b/app/views/onboarding/Onboarding5.js index c1ef6c2a12..8f0053ce21 100644 --- a/app/views/onboarding/Onboarding5.js +++ b/app/views/onboarding/Onboarding5.js @@ -1,251 +1,72 @@ import React, { Component } from 'react'; import { Dimensions, + FlatList, ImageBackground, StatusBar, StyleSheet, - Text, View, } from 'react-native'; -import { - PERMISSIONS, - RESULTS, - check, - checkNotifications, - request, - requestNotifications, -} from 'react-native-permissions'; -import { SvgXml } from 'react-native-svg'; import BackgroundImage from './../../assets/images/launchScreenBackground.png'; -import { isPlatformiOS } from './../../Util'; -import IconDenied from '../../assets/svgs/permissionDenied'; -import IconGranted from '../../assets/svgs/permissionGranted'; -import IconUnknown from '../../assets/svgs/permissionUnknown'; import ButtonWrapper from '../../components/ButtonWrapper'; +import { Type, Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; import fontFamily from '../../constants/fonts'; -import { PARTICIPATE } from '../../constants/storage'; -import { SetStoreData } from '../../helpers/General'; +import { AUTHORITY_SOURCE_SETTINGS } from '../../constants/storage'; +import { GetStoreData } from '../../helpers/General'; import languages from '../../locales/languages'; -import { Typography } from '../../components/Typography'; const width = Dimensions.get('window').width; -const PermissionStatusEnum = { - UNKNOWN: 0, - GRANTED: 1, - DENIED: 2, -}; - -const PermissionDescription = ({ title, status, ...props }) => { - let icon; - switch (status) { - case PermissionStatusEnum.UNKNOWN: - icon = IconUnknown; - break; - case PermissionStatusEnum.GRANTED: - icon = IconGranted; - break; - case PermissionStatusEnum.DENIED: - icon = IconDenied; - break; - } - return ( - - {title} - - - ); -}; - class Onboarding extends Component { constructor(props) { super(props); + this.state = { - notificationPermission: PermissionStatusEnum.UNKNOWN, - locationPermission: PermissionStatusEnum.UNKNOWN, + selectedAuthorities: [], }; - this.checkLocationStatus(); - this.checkNotificationStatus(); - } - - isLocationChecked() { - return this.state.locationPermission !== PermissionStatusEnum.UNKNOWN; } - isNotificationChecked() { - return this.state.notificationPermission !== PermissionStatusEnum.UNKNOWN; + componentDidMount() { + this.props.navigation.addListener('focus', this.getAuthorities); + this.getAuthorities(); } - checkLocationStatus() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; - } - check(locationPermission) - .then(result => { - switch (result) { - case RESULTS.GRANTED: - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }) - .catch(error => { - console.log('error checking location: ' + error); - }); - } - - checkNotificationStatus() { - checkNotifications().then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; + getAuthorities = () => { + GetStoreData(AUTHORITY_SOURCE_SETTINGS, false).then(result => { + if (result !== null) { + console.log('Retrieving settings from async storage:'); + console.log(result); + this.setState({ + selectedAuthorities: result, + }); + } else { + console.log('No stored authority settings.'); } }); - } + }; - requestLocation() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; - } - request(locationPermission).then(result => { - switch (result) { - case RESULTS.GRANTED: - console.log('Location granted'); - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); - } + onGoToSettingsPress = () => { + this.props.navigation.navigate('ChooseProviderScreen'); + }; - requestNotification() { - requestNotifications(['alert', 'badge', 'sound']).then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); - } - - buttonPressed() { - if (!this.isLocationChecked()) { - this.requestLocation(); - } else if (!this.isNotificationChecked()) { - this.requestNotification(); - } else { - SetStoreData(PARTICIPATE, 'true'); // replaces "start" button - SetStoreData('ONBOARDING_DONE', true); - this.props.navigation.replace('LocationTrackingScreen'); - } - } - - getTitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_header'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_header'); - } else { - return languages.t('label.launch_done_header'); - } - } - - getTitleTextView() { - if (!this.isLocationChecked() || !this.isNotificationChecked()) { - return {this.getTitleText()}; - } else { - return {this.getTitleText()}; - } - } + onButtonPress = () => { + this.props.navigation.replace('Onboarding6'); + }; - getSubtitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_subheader'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_subheader'); - } else { - return languages.t('label.launch_done_subheader'); - } - } - - getLocationPermission() { + renderAuthorities = () => { return ( - <> - - - - + ( + + {item.key} + + )} + /> ); - } - - getNotificationsPermissionIfIOS() { - if (isPlatformiOS()) { - return ( - <> - - - - ); - } - return; - } - - getButtonText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_enable_location'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_enable_notif'); - } else { - return languages.t('label.launch_finish_set_up'); - } - } + }; render() { return ( @@ -253,23 +74,34 @@ class Onboarding extends Component { - - {this.getTitleTextView()} - {this.getSubtitleText()} - - {this.getLocationPermission()} - {this.getNotificationsPermissionIfIOS()} - - + + {languages.t('label.choose_provider_title')} + + + {languages.t('label.choose_provider_subtitle')} + + + {this.renderAuthorities()} + + @@ -296,19 +128,9 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignSelf: 'center', }, - bigHeaderText: { - color: Colors.WHITE, - fontSize: 52, - lineHeight: 48.5, - paddingTop: 52 - 48.5, // lineHeight hack - width: width * 0.7, - fontFamily: fontFamily.primaryMedium, - }, headerText: { color: Colors.WHITE, - fontSize: 26, width: width * 0.8, - fontFamily: fontFamily.primaryMedium, }, subheaderText: { marginTop: '3%', @@ -317,16 +139,8 @@ const styles = StyleSheet.create({ width: width * 0.55, fontFamily: fontFamily.primaryRegular, }, - statusContainer: { - marginTop: '5%', - }, - divider: { - backgroundColor: Colors.DIVIDER, - height: 1, - marginVertical: '3%', - }, spacer: { - marginVertical: '5%', + marginVertical: 7.5, }, footerContainer: { position: 'absolute', @@ -334,18 +148,24 @@ const styles = StyleSheet.create({ marginBottom: '10%', alignSelf: 'center', }, - permissionContainer: { + item: { + fontFamily: fontFamily.primaryRegular, + fontSize: 16, + padding: 10, + maxWidth: '90%', + color: Colors.WHITE, + }, + flatlistRowView: { flexDirection: 'row', justifyContent: 'space-between', + paddingTop: 7, + paddingBottom: 5, + borderBottomWidth: 1, + borderColor: Colors.SILVER, + overflow: 'scroll', }, - permissionTitle: { - color: Colors.WHITE, - fontSize: 16, - alignSelf: 'center', - fontFamily: fontFamily.primaryRegular, - }, - permissionIcon: { - alignSelf: 'center', + listContainer: { + height: '25%', }, }); diff --git a/app/views/onboarding/Onboarding6.js b/app/views/onboarding/Onboarding6.js new file mode 100644 index 0000000000..b4ef241d68 --- /dev/null +++ b/app/views/onboarding/Onboarding6.js @@ -0,0 +1,360 @@ +import React, { Component } from 'react'; +import { + Dimensions, + ImageBackground, + StatusBar, + StyleSheet, + View, +} from 'react-native'; +import { + PERMISSIONS, + RESULTS, + check, + checkNotifications, + request, + requestNotifications, +} from 'react-native-permissions'; +import { SvgXml } from 'react-native-svg'; + +import BackgroundImage from './../../assets/images/launchScreenBackground.png'; +import IconDenied from '../../assets/svgs/permissionDenied'; +import IconGranted from '../../assets/svgs/permissionGranted'; +import IconUnknown from '../../assets/svgs/permissionUnknown'; +import ButtonWrapper from '../../components/ButtonWrapper'; +import { Type, Typography } from '../../components/Typography'; +import Colors from '../../constants/colors'; +import fontFamily from '../../constants/fonts'; +import { PARTICIPATE } from '../../constants/storage'; +import { SetStoreData } from '../../helpers/General'; +import languages from '../../locales/languages'; +import { isPlatformiOS } from '../../Util'; + +const width = Dimensions.get('window').width; + +const PermissionStatusEnum = { + UNKNOWN: 0, + GRANTED: 1, + DENIED: 2, +}; + +const PermissionDescription = ({ title, status }) => { + let icon; + switch (status) { + case PermissionStatusEnum.UNKNOWN: + icon = IconUnknown; + break; + case PermissionStatusEnum.GRANTED: + icon = IconGranted; + break; + case PermissionStatusEnum.DENIED: + icon = IconDenied; + break; + } + return ( + + {title} + + + ); +}; + +class Onboarding extends Component { + constructor(props) { + super(props); + this.state = { + notificationPermission: PermissionStatusEnum.UNKNOWN, + locationPermission: PermissionStatusEnum.UNKNOWN, + }; + this.checkLocationStatus(); + this.checkNotificationStatus(); + } + + isLocationChecked() { + return this.state.locationPermission !== PermissionStatusEnum.UNKNOWN; + } + + isNotificationChecked() { + return this.state.notificationPermission !== PermissionStatusEnum.UNKNOWN; + } + + checkLocationStatus() { + // NEED TO TEST ON ANNDROID + let locationPermission; + if (isPlatformiOS()) { + locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; + } else { + locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + } + check(locationPermission) + .then(result => { + switch (result) { + case RESULTS.GRANTED: + this.setState({ + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + locationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }) + .catch(error => { + console.log('error checking location: ' + error); + }); + } + + checkNotificationStatus() { + checkNotifications().then(({ status }) => { + switch (status) { + case RESULTS.GRANTED: + this.setState({ + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }); + } + + requestLocation() { + // NEED TO TEST ON ANNDROID + let locationPermission; + if (isPlatformiOS()) { + locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; + } else { + locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + } + request(locationPermission).then(result => { + switch (result) { + case RESULTS.GRANTED: + console.log('Location granted'); + this.setState({ + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + locationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }); + } + + requestNotification() { + requestNotifications(['alert', 'badge', 'sound']).then(({ status }) => { + switch (status) { + case RESULTS.GRANTED: + this.setState({ + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }); + } + + buttonPressed() { + if (!this.isLocationChecked()) { + this.requestLocation(); + } else if (!this.isNotificationChecked()) { + this.requestNotification(); + } else { + SetStoreData(PARTICIPATE, 'true'); // replaces "start" button + SetStoreData('ONBOARDING_DONE', true); + this.props.navigation.replace('LocationTrackingScreen'); + } + } + + getTitleText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_location_header'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_notif_header'); + } else { + return languages.t('label.launch_done_header'); + } + } + + getTitleTextView() { + if (!this.isLocationChecked() || !this.isNotificationChecked()) { + return ( + + {this.getTitleText()} + + ); + } else { + return ( + + {this.getTitleText()} + + ); + } + } + + getSubtitleText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_location_subheader'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_notif_subheader'); + } else { + return languages.t('label.launch_done_subheader'); + } + } + + getLocationPermission() { + return ( + <> + + + + + ); + } + + getNotificationsPermissionIfIOS() { + if (isPlatformiOS()) { + return ( + <> + + + + ); + } + return; + } + + getButtonText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_enable_location'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_enable_notif'); + } else { + return languages.t('label.launch_finish_set_up'); + } + } + + render() { + return ( + + + + + + {this.getTitleTextView()} + + {this.getSubtitleText()} + + + {this.getLocationPermission()} + {this.getNotificationsPermissionIfIOS()} + + + + + + + + + ); + } +} + +const styles = StyleSheet.create({ + backgroundImage: { + width: '100%', + height: '100%', + resizeMode: 'cover', + flex: 1, + }, + mainContainer: { + flex: 1, + }, + contentContainer: { + width: width * 0.9, + flex: 1, + justifyContent: 'center', + alignSelf: 'center', + }, + bigHeaderText: { + color: Colors.WHITE, + lineHeight: 48.5, + paddingTop: 52 - 48.5, // lineHeight hack + width: width * 0.7, + }, + headerText: { + color: Colors.WHITE, + width: width * 0.8, + }, + subheaderText: { + marginTop: '3%', + color: Colors.WHITE, + fontSize: 15, + width: width * 0.55, + fontFamily: fontFamily.primaryRegular, + }, + statusContainer: { + marginTop: '5%', + }, + divider: { + backgroundColor: Colors.DIVIDER, + height: 1, + marginVertical: '3%', + }, + spacer: { + marginVertical: '5%', + }, + footerContainer: { + position: 'absolute', + bottom: 0, + marginBottom: '10%', + alignSelf: 'center', + }, + permissionContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + permissionTitle: { + color: Colors.WHITE, + fontSize: 16, + alignSelf: 'center', + fontFamily: fontFamily.primaryRegular, + }, + permissionIcon: { + alignSelf: 'center', + }, +}); + +export default Onboarding; diff --git a/e2e/Onboarding.spec.js b/e2e/Onboarding.spec.js index d6c17abd01..058cd7d6f8 100644 --- a/e2e/Onboarding.spec.js +++ b/e2e/Onboarding.spec.js @@ -3,6 +3,7 @@ import Onboarding1 from './pages/Onboarding1.po.js'; import Onboarding2 from './pages/Onboarding2.po.js'; import Onboarding3 from './pages/Onboarding3.po.js'; import Onboarding4 from './pages/Onboarding4.po.js'; +import Onboarding5 from './pages/Onboarding5.po.js'; describe('Onboarding visual appearance', () => { it('Navigates through the onboarding without visual regression', async () => { @@ -26,6 +27,10 @@ describe('Onboarding visual appearance', () => { await Onboarding4.takeScreenshot(); await Onboarding4.tapButton(); + await Onboarding5.isOnScreen(); + await Onboarding5.takeScreenshot(); + await Onboarding5.tapButton(); + await EnableLocation.takeScreenshot(); await EnableLocation.tapButton(); await EnableLocation.takeMenuScreenshot(); diff --git a/e2e/helpers/onboarding.js b/e2e/helpers/onboarding.js index a4a35d05b8..b6dc494083 100644 --- a/e2e/helpers/onboarding.js +++ b/e2e/helpers/onboarding.js @@ -2,6 +2,7 @@ import Onboarding1 from '../pages/Onboarding1.po.js'; import Onboarding2 from '../pages/Onboarding2.po.js'; import Onboarding3 from '../pages/Onboarding3.po.js'; import Onboarding4 from '../pages/Onboarding4.po.js'; +import Onboarding5 from '../pages/Onboarding5.po.js'; export const navigateThroughOnboarding = async () => { await Onboarding1.isOnScreen(); @@ -15,4 +16,7 @@ export const navigateThroughOnboarding = async () => { await Onboarding4.isOnScreen(); await Onboarding4.tapButton(); + + await Onboarding5.isOnScreen(); + await Onboarding5.tapButton(); }; diff --git a/e2e/pages/Onboarding5.po.js b/e2e/pages/Onboarding5.po.js new file mode 100644 index 0000000000..7576b6ae42 --- /dev/null +++ b/e2e/pages/Onboarding5.po.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +const buttonLabel = 'Next'; +const screenTitleTestId = 'Header'; +const screenSubtitle = 'Subheader'; +const screenshotText = 'Onboarding - Page 5'; + +class Onboarding5 { + async tapButton() { + await element(by.label(buttonLabel)).tap(); + } + + async takeScreenshot() { + await device.takeScreenshot(screenshotText); + } + + async isOnScreen() { + // eslint-disable-next-line jest/no-standalone-expect + await expect(element(by.id(screenTitleTestId))).toBeVisible(); + await expect(element(by.id(screenSubtitle))).toBeVisible(); + } +} + +export default new Onboarding5(); diff --git a/e2e/pages/Onboarding6.po.js b/e2e/pages/Onboarding6.po.js new file mode 100644 index 0000000000..7458ea9847 --- /dev/null +++ b/e2e/pages/Onboarding6.po.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +const buttonLabel = 'Next'; +const screenTitleTestId = 'Header'; +const screenSubtitle = 'Subheader'; +const screenshotText = 'Onboarding - Page 6'; + +class Onboarding6 { + async tapButton() { + await element(by.label(buttonLabel)).tap(); + } + + async takeScreenshot() { + await device.takeScreenshot(screenshotText); + } + + async isOnScreen() { + // eslint-disable-next-line jest/no-standalone-expect + await expect(element(by.id(screenTitleTestId))).toBeVisible(); + await expect(element(by.id(screenSubtitle))).toBeVisible(); + } +} + +export default new Onboarding6(); From 8b943a684f700888ba62d24658f3153ebdb7127d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 23 Apr 2020 22:26:20 -0700 Subject: [PATCH 13/60] Add version check service (#653) * Add version check service * Update app/locales/en.json Co-Authored-By: Patrick Erichsen * Update store links * Fix tests * Yaml support * Update link to yaml file Co-authored-by: Patrick Erichsen --- App.js | 2 + app/Util.js | 5 ++ app/constants/versionCheck.js | 6 ++ app/locales/en.json | 10 ++- app/services/VersionCheckService.js | 104 ++++++++++++++++++++++++++++ ios/COVIDSafePaths/Info.plist | 6 ++ jestSetupFile.js | 5 +- 7 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 app/constants/versionCheck.js create mode 100644 app/services/VersionCheckService.js diff --git a/App.js b/App.js index fb63aef3a9..b8085e2af1 100644 --- a/App.js +++ b/App.js @@ -12,10 +12,12 @@ import SplashScreen from 'react-native-splash-screen'; import { Theme } from './app/constants/themes'; import Entry from './app/Entry'; +import VersionCheckService from './app/services/VersionCheckService'; const App = () => { useEffect(() => { SplashScreen.hide(); + VersionCheckService.start(); }, []); return ( diff --git a/app/Util.js b/app/Util.js index 0d21f3390a..d6ac48d3e5 100644 --- a/app/Util.js +++ b/app/Util.js @@ -13,6 +13,11 @@ export function nowStr() { return dayjs().format('H:mm'); } +export const isVersionGreater = (source, target) => + source + .split('.') + .some((val, index) => parseInt(val) > parseInt(target.split('.')[index])); + export default { isPlatformiOS, isPlatformAndroid, diff --git a/app/constants/versionCheck.js b/app/constants/versionCheck.js new file mode 100644 index 0000000000..8fc46b9480 --- /dev/null +++ b/app/constants/versionCheck.js @@ -0,0 +1,6 @@ +export const APPSTORE_URL = 'itms-apps://itunes.apple.com/us/app/id1508266966'; +export const PLAYSTORE_URL = 'market://details?id=org.pathcheck.covidsafepaths'; +export const VERSION_URL = + 'https://rawcdn.githack.com/tripleblindmarket/covid-safe-paths/0008e269c355e14bb216bb6e69cf4b89e25012b2/versions.yaml'; +export const YAML_MANDATORY_VERSION_KEY = 'mandatory update for less than'; +export const YAML_LATEST_VERSION_KEY = 'latest version'; diff --git a/app/locales/en.json b/app/locales/en.json index 77a4bae8c4..ec4aa3bb58 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -109,5 +109,13 @@ "what_does_this_mean": "What does this mean?", "what_if_no_symptoms_para": "If you have no symptoms but still would like to be tested you can go to your nearest testing site.\n\nIndividuals who don't exhibit symptoms can sometimes still carry the infection and infect others. Being careful about social distancing and coming in contact with large groups or at risk individuals (the elderly, those with significant other medical issues) is important to manage both your risk and the risk to others.", "what_if_no_symptoms": "What if I’m not showing symptoms?" + }, + "version_update": { + "push_notification_title": "Application is outdated", + "push_notification_message": "Please update your application", + "alert_label": "COVID Safe Paths is outdated", + "alert_sublabel": "Update to a new version?", + "update": "Update", + "later": "Later" } -} \ No newline at end of file +} diff --git a/app/services/VersionCheckService.js b/app/services/VersionCheckService.js new file mode 100644 index 0000000000..ed7bdf544b --- /dev/null +++ b/app/services/VersionCheckService.js @@ -0,0 +1,104 @@ +import Yaml from 'js-yaml'; +import { Alert, Linking } from 'react-native'; +import BackgroundTimer from 'react-native-background-timer'; +import PushNotification from 'react-native-push-notification'; + +import { version as currentVersion } from '../../package.json'; +import { + APPSTORE_URL, + PLAYSTORE_URL, + VERSION_URL, + YAML_LATEST_VERSION_KEY, + YAML_MANDATORY_VERSION_KEY, +} from '../constants/versionCheck'; +import languages from '../locales/languages'; +import { isPlatformiOS, isVersionGreater } from '../Util'; + +const POLL_INTERVAL = 24 * 60 * 60 * 1000; //one day + +const updateLink = isPlatformiOS() ? APPSTORE_URL : PLAYSTORE_URL; +let isPollStarted = false; +//make sure we show only one notification +let notificationShown = false; + +export default class VersionCheckService { + static start() { + if (!isPollStarted) { + this.checkUpdate(); + // currenly BackgroundTimer is not used anywhere else. + // But this thing should be called only once, otherwise it behaves not as expected. + // It worths making a separate service. + BackgroundTimer.runBackgroundTimer( + () => this.checkUpdate(), + POLL_INTERVAL, + ); + isPollStarted = true; + } + } + + static stop() { + BackgroundTimer.stopBackgroundTimer(); + } + + static async checkUpdate() { + if (notificationShown) return; + let mandatoryVersion, latestVersion; + try { + const response = await fetch(VERSION_URL); + const responseText = await response.text(); + const parsedFile = Yaml.safeLoad(responseText); + mandatoryVersion = parsedFile[YAML_MANDATORY_VERSION_KEY]; + latestVersion = parsedFile[YAML_LATEST_VERSION_KEY]; + } catch (err) { + console.log(err); + return; + } + + if (!isVersionGreater(latestVersion, currentVersion)) return; + + const isMandatoryUpdate = isVersionGreater( + mandatoryVersion, + currentVersion, + ); + this.showNotifications(isMandatoryUpdate); + } + + static showNotifications(isMandatoryUpdate) { + const updateOption = { + text: languages.t('version_update.update'), + onPress: () => { + notificationShown = false; + Linking.canOpenURL(updateLink).then( + supported => supported && Linking.openURL(updateLink), + err => console.log(err), + ); + }, + }; + const laterOption = { + text: languages.t('version_update.later'), + onPress: () => { + notificationShown = false; + console.log('User does not want to update the app'); + }, + style: 'cancel', + }; + + const alertOptions = [updateOption]; + + if (isMandatoryUpdate) { + PushNotification.localNotification({ + title: languages.t('version_update.push_notification_title'), + message: languages.t('version_update.push_notification_message'), + }); + } else { + alertOptions.push(laterOption); + } + + notificationShown = true; + Alert.alert( + languages.t('version_update.alert_label'), + languages.t('version_update.alert_sublabel'), + alertOptions, + ); + } +} diff --git a/ios/COVIDSafePaths/Info.plist b/ios/COVIDSafePaths/Info.plist index aa6d660ad6..d14667cc1a 100644 --- a/ios/COVIDSafePaths/Info.plist +++ b/ios/COVIDSafePaths/Info.plist @@ -2,6 +2,12 @@ + LSApplicationQueriesSchemes + + itms-apps + + LSApplicationCategoryType + BGTaskSchedulerPermittedIdentifiers com.transistorsoft.fetch diff --git a/jestSetupFile.js b/jestSetupFile.js index 5fb0549d2c..064a621f7c 100644 --- a/jestSetupFile.js +++ b/jestSetupFile.js @@ -17,7 +17,10 @@ jest.mock( ); jest.mock('react-native-share', () => 'Share'); jest.mock('rn-fetch-blob', () => 'Blob'); -jest.mock('react-native-background-timer', () => 'BackgroundTimer'); +jest.mock('react-native-background-timer', () => ({ + runBackgroundTimer: () => {}, + stopBackgroundTimer: () => {}, +})); jest.mock('react-native-popup-menu', () => ({ Menu: 'Menu', MenuProvider: 'MenuProvider', From 8798711562b37a3213da97cd054c2e7d423144c4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 23 Apr 2020 22:52:26 -0700 Subject: [PATCH 14/60] Change naming in yaml, change yaml link (#661) --- app/constants/versionCheck.js | 6 +++--- versions.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/constants/versionCheck.js b/app/constants/versionCheck.js index 8fc46b9480..03fd15da72 100644 --- a/app/constants/versionCheck.js +++ b/app/constants/versionCheck.js @@ -1,6 +1,6 @@ export const APPSTORE_URL = 'itms-apps://itunes.apple.com/us/app/id1508266966'; export const PLAYSTORE_URL = 'market://details?id=org.pathcheck.covidsafepaths'; export const VERSION_URL = - 'https://rawcdn.githack.com/tripleblindmarket/covid-safe-paths/0008e269c355e14bb216bb6e69cf4b89e25012b2/versions.yaml'; -export const YAML_MANDATORY_VERSION_KEY = 'mandatory update for less than'; -export const YAML_LATEST_VERSION_KEY = 'latest version'; + 'https://raw.githack.com/tripleblindmarket/covid-safe-paths/develop/versions.yaml'; +export const YAML_MANDATORY_VERSION_KEY = 'mandatory upgrade version'; +export const YAML_LATEST_VERSION_KEY = 'upgrade version'; diff --git a/versions.yaml b/versions.yaml index 8e79cf5de3..4b23c9a500 100644 --- a/versions.yaml +++ b/versions.yaml @@ -1,4 +1,4 @@ # Versions less than specified will receive a gentle update notification (dismissable alert only) -latest version: "0.9.5" +upgrade version: "0.9.5" # Versions less than specified will receive a mandatory update notification (push + non-dismissable alert) -mandatory update for less than: "0.9.4" +mandatory upgrade version: "0.9.4" From 79ff0cec5481888b81e4adcf7b61515f8d8007f4 Mon Sep 17 00:00:00 2001 From: kpugsley Date: Fri, 24 Apr 2020 01:05:19 -0500 Subject: [PATCH 15/60] change to rawcdn for the versions.yaml file (#662) Co-authored-by: Ken Pugsley <> --- app/constants/versionCheck.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/constants/versionCheck.js b/app/constants/versionCheck.js index 03fd15da72..c13a7058fd 100644 --- a/app/constants/versionCheck.js +++ b/app/constants/versionCheck.js @@ -1,6 +1,6 @@ export const APPSTORE_URL = 'itms-apps://itunes.apple.com/us/app/id1508266966'; export const PLAYSTORE_URL = 'market://details?id=org.pathcheck.covidsafepaths'; export const VERSION_URL = - 'https://raw.githack.com/tripleblindmarket/covid-safe-paths/develop/versions.yaml'; + 'https://rawcdn.githack.com/tripleblindmarket/covid-safe-paths/master/versions.yaml'; export const YAML_MANDATORY_VERSION_KEY = 'mandatory upgrade version'; export const YAML_LATEST_VERSION_KEY = 'upgrade version'; From 714b359384bd2ea854b1b4d2f843f49c1a37a338 Mon Sep 17 00:00:00 2001 From: Sam Stowers Date: Fri, 24 Apr 2020 03:41:04 -0500 Subject: [PATCH 16/60] Add an EULA to the onboarding process (#638) ## Description Actual EULA text still needed, but the UI for everything is implemented - screenshots attached below. ## How to test Only tested on one resolution on Android, so making sure things behave as expected on other devices (especially high resolution devices and iPhones) would be helpful. ## Screenshots ![Screenshot from 2020-04-22 15-39-18](https://user-images.githubusercontent.com/6233577/80032551-f6538280-84b0-11ea-8072-db03950eaac1.png) ![Screenshot from 2020-04-22 15-42-14](https://user-images.githubusercontent.com/6233577/80032564-f8b5dc80-84b0-11ea-9f83-0f266e51fa1f.png) --- app/assets/images/boxCheckedIcon.png | Bin 0 -> 1384 bytes app/assets/images/boxUncheckedIcon.png | Bin 0 -> 611 bytes app/components/Button.js | 4 +- app/components/Checkbox.js | 22 + app/components/EulaModal.js | 161 +++++ app/locales/en.json | 16 +- app/locales/eula/en_html.js | 691 ++++++++++++++++++++++ app/locales/eula/ht_html.js | 775 +++++++++++++++++++++++++ app/locales/ht.json | 7 +- app/views/onboarding/Onboarding1.js | 18 +- e2e/Onboarding.spec.js | 5 + e2e/UnsignedEula.spec.js | 19 + e2e/helpers/onboarding.js | 4 + e2e/pages/SignEula.po.js | 20 + 14 files changed, 1721 insertions(+), 21 deletions(-) create mode 100644 app/assets/images/boxCheckedIcon.png create mode 100644 app/assets/images/boxUncheckedIcon.png create mode 100644 app/components/Checkbox.js create mode 100644 app/components/EulaModal.js create mode 100644 app/locales/eula/en_html.js create mode 100644 app/locales/eula/ht_html.js create mode 100644 e2e/UnsignedEula.spec.js create mode 100644 e2e/pages/SignEula.po.js diff --git a/app/assets/images/boxCheckedIcon.png b/app/assets/images/boxCheckedIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..d39d6783812a8fef2be9eb0bca2038f4541a31fd GIT binary patch literal 1384 zcmbVM`#aMM9R8|hvB@o@vpqV>%%xRwT*lZuI-A>EDkPUVYD~GDHVp^E5L0@Ni8S|1 znX_C%g@}}>7Ac2OS+bbB$0e(cv;Kt6^StlpectzZKJSljmIo29qP$-j000#yf}@vQ zNk6z-QGO?H(yip86iqlo1prf*A5^Hua!0SJw|U|a>G?i>GigJo#SFrZ^e^j)g}32Dh5d>2OG~YB z){ps9ZXah8z7|s#>5JJFdn#t6_fP-hy%cWoxpC?q*5ODEQp@KSf1Dk>&s!&{d`yoV z8agtydI6E!wPe`6AE&0r8;*3-&VKgG!$_rMp{fcdkZ6~aD@=8+{Idz9zQaFQ#0E_m zR7#{Fnbuq+OW7HN#-~yzH;G2OQ}GA_3CAUwW`-DWuc)*@e#3%6OUDN4!~j-ndj+5WbGa$lkG=kHN|xOvHDr3wF)U3m_|( zEYHOtgX|=#iHx;rZ=p^9bjA#G<-@75wsdb6RHUO|!r%&-ejR@&FnBSg5sE|r6f^mf zYrvdP)IWd`kcFVlZyd_PjcexTIVl>#W!2|li<1Tm-&gjIl1ZRVae=+poxGU2dAh^S zU%MFFr4@P%43QG|Nuqww?aQt(x-F$&M7=9Zjxak6py4!I#KKg2NTfwbp|)^v&9hP% zB3g>Q>xk;(0m!yK8Ab3&Y!E-qhflFwm5)*@E}o*jSx3ceP2h#x0c0Q9j{@7>F1n*w>jVw^w^&G$u6<2kVY%1bz;>F&*r&*o9A-^ z=LNM)K1oAbJnllwjx;2Ku=Y9m4NWGvx{T>g!{3ZD zV+77K?KRZP`Aa>46p$RQyp7@STOM3f|C|GE4+v^NVNvx#{>Oms+B&*!n!GonI=%h# z8R*;&XjCj>Smrx?rGN`IY1H)}eMxCVVJUMj^R(Zk7HTpuYuiapy0qv6UD{*&?C_Ls zH4%-rxD{hR21Q#5zy|=t>TuMOL}H@Ln~#gKSK2qm^{ZRb=1U=r( z$yZl(X$y9D(_omTKD);k|=Q&Tr~cM06E%y@e^C#LRD~` zOF`KQpy&qZIkmEZjYP+eUP}%Rd_T*#KD+9=1#=JYpcBuR?dE5QYGwZ{u@{f8TwGwYWJ9;Sq};u zVTxF0pi7BKZxMl=hR|uvkmuK2~a@WQwdd#=$HZ2UF6?Yy|TtOkxwK ze47yMYO9seGwiMNE(`?Dwb#<{_iWW!l~^?U!aCoC)#TbLp)&8NU3d!fsa>5z-ZD94 r3HM+v*9{<*I?7mN@cLytk-ZQFt{O1gFkg8hA4b6G1kth1ft30m_DO6E literal 0 HcmV?d00001 diff --git a/app/assets/images/boxUncheckedIcon.png b/app/assets/images/boxUncheckedIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..1ece7a6db4c8fe10632ea26d24beb76e96209203 GIT binary patch literal 611 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGooCO|{#S9EO-XP4l)OOlRpde#$ zkh>GZx^prwfgF}}M_)$E)e-c@Naz8v>978G?-`=(LyW=3^@Np)is*|>Ai?EI> zS9s)og=!`XuRMn8<~P2RgjLKV%JqUas4J)lZE9&@=a**++#n=hclNjG>xr>{@86nt z&d}!4w)%Z}xw-ORYVBmEU0!hg!jT!X_wA1e3Ef&(p>$7nP1x$YCsV#>Nc`Epzl3t#T4|16|9HT36m_ICSaH&Z?>JY#cYTkdkh zefFGEvFE0Lo^omTVg;9;mj+K|YQH2ra;#*RXfVEeujJ_Ry?Ylt|6N`6>&Z`%79)l; z3X_1Y<+*ec^jwY-9HN>}0;?I3D+l}|%Y`gaLU*z?pJIfDAeA;wo*O@(Q9zDLCTWut( vXVqs|BROsJg6kXfX3zb!Z*S$3`@5N#?31TdX(t%~(*%R3tDnm{r-UW|#MJ2g literal 0 HcmV?d00001 diff --git a/app/components/Button.js b/app/components/Button.js index c5f8098e11..7e1888394d 100644 --- a/app/components/Button.js +++ b/app/components/Button.js @@ -18,6 +18,7 @@ class Button extends React.Component { buttonStyle, buttonHeight = 54, borderColor, + disabled, } = this.props; return ( + accessibilityRole='button' + disabled={disabled}> { + return ( + + + {label} + + ); +}; diff --git a/app/components/EulaModal.js b/app/components/EulaModal.js new file mode 100644 index 0000000000..1c6ebaf703 --- /dev/null +++ b/app/components/EulaModal.js @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import WebView from 'react-native-webview'; + +import closeIcon from '../assets/images/closeIcon.png'; +import Colors from '../constants/colors'; +import { Theme } from '../constants/themes'; +import en_html from '../locales/eula/en_html'; +import ht_html from '../locales/eula/ht_html'; +import ButtonWrapper from './ButtonWrapper'; +import { Checkbox } from './Checkbox'; +import { Typography } from './Typography'; + +const EULA_FILES = { + en: en_html, + ht: ht_html, +}; + +const webViewScrollDetection = ` + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If 'immediate' is passed, trigger the function on the + // leading edge, instead of the trailing. + function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; + }; + + function scrollHandler() { + if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { + window.ReactNativeWebView.postMessage('end'); + } else { + window.ReactNativeWebView.postMessage('scroll'); + } + } + + setTimeout(() => { + window.addEventListener('scroll', debounce(scrollHandler, 150)); + }, 300); + true; +`; + +export const EulaModal = ({ selectedLocale, continueFunction }) => { + const [modalVisible, setModalVisibility] = useState(false); + const [boxChecked, toggleCheckbox] = useState(false); + const [hasScrolledToEnd, setScrolledToEnd] = useState(); + const { t } = useTranslation(); + + // reset the scroll check if language changes + useEffect(() => { + setScrolledToEnd(false); + }, [selectedLocale]); + + /** + * Set scrolled to end when end reached, and never untoggle it if scrolling + * back up + */ + const handleWebViewMessage = event => { + if (!hasScrolledToEnd && event.nativeEvent.data === 'end') { + setScrolledToEnd(true); + } + }; + + // Pull the EULA in the correct language, with en as fallback + const html = EULA_FILES[selectedLocale] || en_html; + + const canContinue = boxChecked && hasScrolledToEnd; + + return ( + <> + setModalVisibility(true)} + buttonColor={Colors.VIOLET} + bgColor={Colors.WHITE} + /> + + + + + setModalVisibility(false)}> + + + + + + + + + toggleCheckbox(!boxChecked)} + checked={boxChecked} + /> + + {t('onboarding.eula_message')} + + { + setModalVisibility(false); + continueFunction(); + }} + /> + + + + + + + ); +}; + +const styles = StyleSheet.create({ + // Container covers the entire screen + container: { + flex: 1, + flexDirection: 'column', + justifyContent: 'space-between', + color: Colors.PRIMARY_TEXT, + backgroundColor: Colors.WHITE, + }, + ctaBox: { + padding: 25, + paddingTop: 0, + backgroundColor: Colors.VIOLET_BUTTON, + }, + closeIcon: { + width: 20, + height: 20, + marginBottom: 6, + opacity: 0.7, + alignSelf: 'flex-end', + }, + smallDescriptionText: { + fontSize: 14, + marginVertical: 12, + }, +}); diff --git a/app/locales/en.json b/app/locales/en.json index ec4aa3bb58..256e047a3f 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -1,13 +1,4 @@ { - "history": { - "no_exposure": "No exposure", - "possible_exposure": "Possible exposure", - "possible_exposure_para": "It is possible you were in contact with or close to someone who tested positive for COVID-19", - "what_does_this_mean": "What does this mean?", - "what_does_this_mean_para": "Based on your GPS history, it is possible that you may have been in contact with or close to somebody who was diagnosed with COVID-19. This does not mean you are infected but that you might be.\n\nFor further information on what you should do you can refer to the Mayo Clinic’s website.", - "what_if_no_symptoms": "What if I’m not showing symptoms?", - "what_if_no_symptoms_para": "If you have no symptoms but still would like to be tested you can go to your nearest testing site.\n\nIndividuals who don't exhibit symptoms can sometimes still carry the infection and infect others. Being careful about social distancing and coming in contact with large groups or at risk individuals (the elderly, those with significant other medical issues) is important to manage both your risk and the risk to others." - }, "label": { "about_title": "About", "authorities_add_button_label": "Add Trusted Source", @@ -94,8 +85,6 @@ "terms_of_use_url": "https://docs.google.com/document/d/1mtdal_pywsKZVMXLHjjj5eKznipPLP8sM1HwFTIhjo0/edit#", "tested_positive_subtitle": "Your private data can be transferred to health authorities, backed up, or otherwise shared.", "tested_positive_title": "Share location history", - "logging_active": "Location Active", - "logging_inactive": "Location Inactive", "language_change_alert_title": "App need to restart in order to use this language", "language_change_alert_proced": "Restart", "language_change_alert_cancel": "Cancel" @@ -110,6 +99,11 @@ "what_if_no_symptoms_para": "If you have no symptoms but still would like to be tested you can go to your nearest testing site.\n\nIndividuals who don't exhibit symptoms can sometimes still carry the infection and infect others. Being careful about social distancing and coming in contact with large groups or at risk individuals (the elderly, those with significant other medical issues) is important to manage both your risk and the risk to others.", "what_if_no_symptoms": "What if I’m not showing symptoms?" }, + "onboarding": { + "eula_checkbox": "I accept the licensing agreement", + "eula_message": "*You must accept in order to use Safe Paths", + "eula_continue": "Continue" + }, "version_update": { "push_notification_title": "Application is outdated", "push_notification_message": "Please update your application", diff --git a/app/locales/eula/en_html.js b/app/locales/eula/en_html.js new file mode 100644 index 0000000000..c13fb29059 --- /dev/null +++ b/app/locales/eula/en_html.js @@ -0,0 +1,691 @@ +export default ` + + + +
+ + + + +

+ Terms of Use +

+

+ Last Updated Date: April 23, 2020 +

+

+ PLEASE READ THIS TERMS OF USE AGREEMENT (THE “TERMS OF USE” OR + “AGREEMENT”) CAREFULLY. THE SAFE PATH MOBILE APPLICATION (THE + “APPLICATION”), AND THE FEATURES AND INFORMATION IN IT ARE CONTROLLED BY PATH + CHECK, INC. (“PCI”). THESE TERMS OF USE GOVERN THE USE OF THE APPLICATION AND + APPLY TO ALL USERS USING THE APPLICATION IN ANY WAY, INCLUDING THE FEATURES THEREIN. BY CLICKING + ON THE “I ACCEPT” BUTTON AND/OR DOWNLOADING THE APPLICATION, YOU REPRESENT THAT (1) YOU HAVE + READ, UNDERSTAND, AND AGREE TO BE BOUND BY THE TERMS OF USE, (2) YOU ARE OF LEGAL AGE TO FORM A + BINDING CONTRACT WITH PCI, AND (3) YOU HAVE THE AUTHORITY TO ENTER INTO THE TERMS OF USE. + IF YOU DO NOT AGREE TO BE BOUND BY THE TERMS OF USE, YOU MAY NOT ACCESS OR USE THE + APPLICATION. +

+

+ PLEASE BE AWARE THAT SECTION 13 OF THIS AGREEMENT, BELOW, CONTAINS PROVISIONS GOVERNING + HOW DISPUTES THAT YOU AND WE HAVE AGAINST EACH OTHER ARE RESOLVED, INCLUDING, WITHOUT + LIMITATION, ANY DISPUTES THAT AROSE OR WERE ASSERTED PRIOR TO THE EFFECTIVE DATE OF THIS + AGREEMENT. IN PARTICULAR, IT CONTAINS AN ARBITRATION AGREEMENT WHICH WILL, WITH LIMITED + EXCEPTIONS, REQUIRE DISPUTES BETWEEN US TO BE SUBMITTED TO BINDING AND FINAL ARBITRATION. + UNLESS YOU OPT OUT OF THE ARBITRATION AGREEMENT: (1) YOU WILL ONLY BE PERMITTED TO PURSUE + DISPUTES OR CLAIMS AND SEEK RELIEF AGAINST US ON AN INDIVIDUAL BASIS, NOT AS A PLAINTIFF OR + CLASS MEMBER IN ANY CLASS OR REPRESENTATIVE ACTION OR PROCEEDING; AND (2) YOU ARE WAIVING YOUR + RIGHT TO PURSUE DISPUTES OR CLAIMS AND SEEK RELIEF IN A COURT OF LAW AND TO HAVE A JURY TRIAL. + +

+

+ ANY DISPUTE, CLAIM OR REQUEST FOR RELIEF RELATING IN ANY WAY TO YOUR USE OF THE + APPLICATION WILL BE GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF + MASSACHUSETTS, CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT TO ANY + PRINCIPLES THAT PROVIDE FOR THE APPLICATION OF THE LAW OF ANY OTHER JURISDICTION. THE UNITED + NATIONS CONVENTION ON CONTRACTS FOR THE INTERNATIONAL SALE OF GOODS IS EXPRESSLY EXCLUDED FROM + THIS AGREEMENT. +

+

+ PLEASE NOTE THAT THIS AGREEMENT IS SUBJECT TO CHANGE BY PCI IN ITS SOLE DISCRETION AT ANY TIME. + When changes are made, PCI will make a new copy of the Terms of Use available within the + Application. We will also update the “Last Updated” date at the top of the Terms of Use. Your + continued use of the Application after a change to the Terms of Use is made constitutes + acceptance. PLEASE REGULARLY CHECK THE APPLICATION TO VIEW THE MOST CURRENT TERMS. +

+
    + +
  1. + USE OF THE APPLICATION. +
      + +
    1. + Definitions. As used in these Terms of Use, the following definitions + apply: +

      + “App User” means an individual natural person who has downloaded the + Application under these Terms of Use. +

      +

      + “Location History” means location data collected from an App User’s + mobile device leveraging GPS, Bluetooth and/or other features or software, that the App + User elects to have recorded and stored within the Application installed on the App + User’s mobile device. +

      +

      + “Publicly Available Safe Places Data” means anonymized maps of public + places where individuals diagnosed with Covid-19 have visited and data files of times + they visited such places, as compiled and created by a third party, and made available + via such third party’s Third Party Safe Places Web App. +

      +

      + “Safe Places Software” means open-source software, available at https://github.com/tripleblindmarket/safe-places, + that facilitates contact tracing and related tasks, and is designed for use by third + parties, such as government agencies. +

      +

      + “Third Party Safe Places Web App” means a web-based application owned + and operated by a third party, such as a government agency, that employs Safe Places + Software to, among other things, publish Publicly Available Safe Places Data. +

      +
        + +
      1. + Application Features and Functionality. The Application is designed + to enable App Users, at their option: + (a) to record their Location History and to store such data locally in the Application + downloaded to their mobile device, (b) to access Publicly Available Safe Places Data + from one or more Third Party Safe Places Web Apps and download it to the Safe Paths + App downloaded on their mobile device, and (c) to share their stored Location History + with third parties that they choose. + +
      2. Application License. The Application and the information and + content available therein are protected by copyright laws throughout the world. + Subject to your compliance with this Agreement, PCI grants you a limited + non-exclusive, non-transferable, non-sublicensable, revocable license to download, + install and use a copy of the Application on a single mobile device or computer that + you own or control and to run such copy of the Application solely for your own + personal or internal business purposes. Certain components or libraries included in or + bundled with the Application constitute open source software and are licensed under + open source licenses. To the extent required by such open source licenses, the terms + of such licenses will apply in lieu of the terms of this Section 1.3, solely with + respect to those components or libraries that are licensed under such open source + licenses. For a copy of such open source licenses, visit https://github.com/tripleblindmarket/covid-safe-paths/blob/develop/LICENSE. + Furthermore, with respect to any Application accessed through or downloaded from the + Apple App Store (an “App Store Sourced Application”), you will only + use the App Store Sourced Application (a) on an Apple-branded product that runs the + iOS (Apple’s proprietary operating system) and (b) as permitted by the “Usage Rules” + set forth in the Apple App Store Terms of Service. Notwithstanding the first sentence + in this section, with respect to any Application accessed through or downloaded from + the Google Play store (a “Google Play Sourced Application”), you may + have additional license rights with respect to use of the Application on a shared + basis within your designated family group. + +
      3. Updates. You understand that to keep the Application most useful + for App Users and to accommodate bug fixes and other technological changes, PCI may + make updates to the Application after you download the Application. These updates will + be made available to App Users from the third party from whom you received the + Application license, e.g., the Apple App Store or Google Play (each, an “App + Store”). You will have the ability, at your option, to download any updates + to the Application that we make freely available through such channels. We do not + require you to install any updates in order for you to continue using the Application + after we make such updates available. This is because we do not retain any information + about you when you download the Application. Therefore, we will not know whether you + have installed any such updates. Accordingly, you acknowledge and agree that if you + elect NOT to install available updates, the Application may not operate in accordance + with publicly available documentation regarding the features and functionality of the + Application, which documentation may be updated after the time you originally + downloaded it. In addition, you may need to update third-party software from time to + time in order to use the Application. Any updates issued by PCI and installed by you + shall be deemed the Application and shall be governed by this Safe Places EULA or any + subsequent end user license agreement accompanying the update. + +
      4. Necessary Equipment and Software. You must provide all equipment + and software necessary to download and use the Application and to connect to any third + party services or sites, including but not limited to, a mobile device that is + suitable to connect with and use the Application. You are solely responsible for any + fees, including Internet connection or mobile fees, that you incur when accessing the + Application. + +
      5. Responsibility for Location History and Other Data Collected and Stored by + You. PCI has no access to the Location History or any other data you + collect, receive and store in the installed Application or otherwise on your mobile + device. Therefore, PCI has no responsibility or liability for the deletion or accuracy + of the Location History or other data you collect, receive or store in the Application + or the failure to store, transmit or receive transmission of Location History or other + data; or the security, privacy, storage, or transmission of other communications + originating with or involving use of the Application. +
      6. +
      + +
    2. OWNERSHIP. +
        + +
      1. + Application. Except with respect to your Location History and other + data you may collect, retrieve from third parties and store in the Application on your + device, you agree that PCI and its suppliers own all rights, title and interest in and + to the Application. You will not remove, alter or obscure any copyright, trademark, + service mark or other proprietary rights notices incorporated in or accompanying the + Application. + +
      2. Trademarks. COVID Safe Paths and all related graphics, logos, + service marks and trade names used on or in connection with the Application or in + connection therewith are the trademarks of PCI and may not be used without permission + in connection with your, or any third-party, products or services. Other trademarks, + service marks and trade names that may appear on or in the Application are the + property of their respective owners. +
      3. +
      + +
    3. COLLECTION AND USE OF YOUR PERSONAL INFORMATION. You acknowledge that + the Application may collect personal information about you (through your choice to store + information in the Application or through automatic technology tools), including your + Location History. You acknowledge that the Application may provide you with opportunities + to share personal information about yourself, including your Location History with others. + All personal information in connection with this Application is subject to our Privacy + Policy. By downloading, installing, using, and providing personal information to or + through this Application, you consent to all actions taken by us with respect to your + personal information in compliance with the Privacy Policy. For some features of the + Application, specific consent is required. In addition, you may choose to disclose, + through other means not associated with the Application, any part of your personal + information to family members, doctors, health care providers, governmental agencies, or + other individuals or entities. We recommend that you make your choices regarding sharing + your personal information, through the Application or otherwise, carefully. We will have + no liability for any consequences that may result because you have released or shared + information, through the Application or otherwise, with a third party. + +
    4. FEEDBACK. If you provide PCI with any ideas, suggestions, documents, + and/or proposals (“Feedback”) via email or another means, you do so at + your own risk and you acknowledge and agree that PCI has no obligations (including without + limitation obligations of confidentiality) with respect to such Feedback. You represent + and warrant that you have all rights necessary to submit the Feedback. You hereby grant to + PCI a fully paid, royalty-free, perpetual, irrevocable, worldwide, non-exclusive, and + fully sublicensable right and license to use, reproduce, perform, display, distribute, + adapt, modify, re-format, create derivative works of, and otherwise commercially or + non-commercially exploit in any manner, any and all Feedback, and to sublicense the + foregoing rights, in connection with the operation and maintenance of the Application, any + other PCI products or services, or PCI’s business. + +
    5. APP STORES. You acknowledge and agree that the availability of the + Application is dependent on the App Store from whom you received the Application license. + You acknowledge that this Agreement is between you and PCI and not with the App Store. + PCI, not the App Store, is solely responsible for the Application, including the content + PCI makes available therein, and the maintenance, support services, and warranty therefor, + and addressing any claims relating thereto (e.g., product liability, legal compliance or + intellectual property infringement). In order to use the Application, you must have access + to a wireless network, and you agree to pay all fees associated with such access. You also + agree to pay all fees (if any) charged by the App Store in connection with the + Application. You agree to comply with, and your license to use the Application is + conditioned upon your compliance with all terms of agreement imposed by the applicable App + Store when using the Application. You acknowledge that the App Store (and its + subsidiaries) are third-party beneficiaries of this Agreement and will have the right to + enforce it. + +
    6. FEES. No fees shall be payable under this Agreement for the rights + granted under this Agreement. You acknowledge and agree that this fee arrangement is made + in consideration for the mutual covenants set forth in this Agreement, including your + obligations hereunder, and the disclaimers, exclusions, and limitations of liability set + forth herein. + +
    7. INDEMNIFICATION. You agree to indemnify and hold PCI, its parents, + subsidiaries, affiliates, officers, employees, volunteers, agents, partners, suppliers, + and licensors (each, a “PCI Party” and collectively, the “PCI + Parties”) harmless from any losses, costs, liabilities and expenses (including + reasonable attorneys’ fees) relating to or arising out of any and all of the following: + (a) your use of, or inability to use the Application; (b) your violation of this + Agreement; (c) your violation of any rights of another party; or (d) your violation of any + applicable laws, rules or regulations. PCI reserves the right, at its own cost, to assume + the exclusive defense and control of any matter otherwise subject to indemnification by + you, in which event you will fully cooperate with PCI in asserting any available defenses. + This provision does not require you to indemnify any of the PCI Parties for any + unconscionable commercial practice by such party or for such party’s fraud, deception, + false promise, misrepresentation or concealment, suppression or omission of any material + fact in connection with the Application provided hereunder. You agree that the provisions + in this section will survive any termination of this Agreement and/or your use of or + access to Application. + +
    8. DISCLAIMER OF WARRANTIES AND CONDITIONS. +
        + +
      1. + As Is. YOU EXPRESSLY UNDERSTAND AND AGREE THAT TO THE EXTENT + PERMITTED BY APPLICABLE LAW, YOUR USE OF THE APPLICATION IS AT YOUR SOLE RISK, AND THE + APPLICATION IS PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS, WITH ALL FAULTS. THE + PCI PARTIES EXPRESSLY DISCLAIM ALL WARRANTIES, REPRESENTATIONS, AND CONDITIONS OF ANY + KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NON-INFRINGEMENT ARISING FROM USE OF THE APPLICATION. +
          + +
        1. + THE PCI PARTIES MAKE NO WARRANTY, REPRESENTATION OR CONDITION THAT: (1) THE + APPLICATION OR ITS FEATURES WILL MEET YOUR REQUIREMENTS; (2) YOUR USE OF THE + APPLICATION WILL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; OR (3) THE + RESULTS THAT MAY BE OBTAINED FROM USE OF THE APPLICATION WILL BE ACCURATE OR + RELIABLE. + +
        2. ANY CONTENT DOWNLOADED FROM OR OTHERWISE ACCESSED THROUGH THE APPLICATION IS + ACCESSED AT YOUR OWN RISK, AND YOU SHALL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO + YOUR PROPERTY, INCLUDING, BUT NOT LIMITED TO, YOUR COMPUTER SYSTEM AND ANY DEVICE + YOU USE TO ACCESS THE APPLICATION, OR ANY OTHER LOSS THAT RESULTS FROM ACCESSING + SUCH CONTENT. + +
        3. THE AVAILABILITY OF THE APPLICATION AND ITS FEATURES MAY BE SUBJECT TO DELAYS, + CANCELLATIONS AND OTHER DISRUPTIONS. PCI MAKES NO WARRANTY, REPRESENTATION OR + CONDITION WITH RESPECT TO THE APPLICATION OR ITS FEATURES, INCLUDING BUT NOT + LIMITED TO, THE QUALITY, EFFECTIVENESS, REPUTATION AND OTHER CHARACTERISTICS + THEREOF. + +
        4. NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED FROM PCI OR THROUGH + THE APPLICATION WILL CREATE ANY WARRANTY NOT EXPRESSLY MADE HEREIN. + +
        5. FROM TIME TO TIME, PCI MAY OFFER NEW “BETA” FEATURES OR TOOLS WITH WHICH ITS + USERS MAY EXPERIMENT. SUCH FEATURES OR TOOLS ARE OFFERED SOLELY FOR EXPERIMENTAL + PURPOSES AND WITHOUT ANY WARRANTY OF ANY KIND, AND MAY BE MODIFIED OR DISCONTINUED + AT PCI’S SOLE DISCRETION. THE PROVISIONS OF THIS SECTION APPLY WITH FULL FORCE TO + SUCH FEATURES OR TOOLS. +
        6. +
        + +
      2. NOT INTENDED AS MEDICAL ADVICE. YOU ACKNOWLEDGE THAT THE + INFORMATION IN THE APPLICATION IS PROVIDED FOR GENERAL INFORMATIONAL PURPOSES ONLY. IT + IS NOT INTENDED AS MEDICAL ADVICE OF ANY KIND NOR IS IT INTENDED TO DIAGNOSE, TREAT, + CURE OR PREVENT ANY DISEASE OR MEDICAL CONDITION. THE INFORMATION PRESENTED IN THE + APPLICATION SHOULD NOT BE INTERPRETED OR CONSTRUED IN ANY WAY AS A REPLACEMENT OR + SUBSTITUTE FOR MEDICAL ADVICE PROVIDED BY YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE + PROVIDER. YOU SHOULD NOT DISREGARD, AVOID OR DELAY OBTAINING MEDICAL ADVICE OR + TREATMENT FROM YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER DUE TO ANY + INFORMATION PROVIDED IN THE APPLICATION. UNDER NO CIRCUMSTANCES SHOULD YOU ALTER YOUR + EXISTING MEDICAL TREATMENT, MEDICATION REGIMEN, OR ANY OTHER RELATED HEALTHCARE + ACTIVITIES BASED ON ANY INFORMATION PROVIDED IN THE APPLICATION. IT IS IMPORTANT FOR + YOU TO DISCUSS YOUR TREATMENT OPTIONS, AND ANY QUESTIONS THAT YOU MAY HAVE, WITH YOUR + DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER. + +
      3. In making the Application available for download, PCI’s goal is to provide + individuals with a useful tool for contact tracing. However, the utility of the + Application’s features is dependent upon a number of factors that are outside the + control of PCI, such as the reliability of GPS sensors, whether individuals are + carrying their mobile devices, as well as the accuracy, reliability, availability, + effectiveness or correct use of individual’s mobile devices and GPS sensors, all of + which are used to create Location History, and for any Publicly Available Safe Places + Data that you may upload into your Application from third party source, the accuracy + and completeness of such data. Your Location History or other data may be unavailable, + inaccurate or incomplete. Use of the Application should not replace your good judgment + and common sense. + +
      4. No Liability for Conduct of Third Parties. YOU ACKNOWLEDGE AND + AGREE THAT PCI PARTIES ARE NOT LIABLE, AND YOU AGREE NOT TO SEEK TO HOLD PCI PARTIES + LIABLE, FOR THE CONDUCT OR OMISSIONS OF THIRD PARTIES, INCLUDING THE ACTIONS OF ANY + THIRD PARTY OPERATING A THIRD PARTY SAFE PLACES WEB APP OR ANY GOVERNMENTAL AGENCY + WITH WHICH YOU CHOOSE TO INTERACT IN CONNECTION WITH YOUR USE OF THE APPLICATION, AND + THAT THE RISK OF INJURY FROM SUCH THIRD PARTIES RESTS ENTIRELY WITH YOU. +
      5. +
      + +
    9. LIMITATION OF LIABILITY. +
        + +
      1. + Disclaimer of Certain Damages. YOU UNDERSTAND AND AGREE THAT IN NO + EVENT SHALL THE PCI PARTIES BE LIABLE FOR ANY LOSS OF PROFITS, PERSONAL INJURY, + PROPERTY DAMAGE, WRONGFUL DEATH, REVENUE OR DATA, INDIRECT, INCIDENTAL, SPECIAL, OR + CONSEQUENTIAL DAMAGES, OR DAMAGES OR COSTS DUE TO LOSS OF PRODUCTION OR USE, BUSINESS + INTERRUPTION, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, IN EACH CASE WHETHER OR NOT + PCI HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, ARISING OUT OF OR IN + CONNECTION WITH THIS AGREEMENT, ON ANY THEORY OF LIABILITY, RESULTING FROM: (1) THE + USE OR INABILITY TO USE THE APPLICATION OR ANY OF ITS ADVERTISED FEATURES; (2) + UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; OR (3) ANY OTHER + MATTER RELATED TO THE APPLICATION, WHETHER BASED ON WARRANTY, COPYRIGHT, CONTRACT, + TORT (INCLUDING NEGLIGENCE), OR ANY OTHER LEGAL THEORY. THE FOREGOING CAP ON LIABILITY + SHALL NOT APPLY TO LIABILITY OF A PCI PARTY FOR (A) DEATH OR PERSONAL INJURY CAUSED BY + A PCI PARTY’S NEGLIGENCE; OR FOR (B) ANY INJURY CAUSED BY A PCI PARTY’S FRAUD OR + FRAUDULENT MISREPRESENTATION. + +
      2. Cap on Liability. TO THE MAXIMUM EXTENT PERMITTED BY LAW, + NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR + ANY DAMAGES ARISING FROM OR RELATED TO THESE TERMS OF USE (FOR ANY CAUSE WHATSOEVER + AND REGARDLESS OF THE FORM OF THE ACTION), WILL AT ALL TIMES BE LIMITED TO A MAXIMUM + OF FIFTY US DOLLARS (U.S. $50). THE EXISTENCE OF MORE THAN ONE CLAIM WILL NOT ENLARGE + THIS LIMIT. YOU AGREE THAT OUR SUPPLIERS WILL HAVE NO LIABILITY OF ANY KIND ARISING + FROM OR RELATING TO THESE TERMS OF USE. + +
      3. Your Data. EXCEPT FOR PCI’S OBLIGATIONS TO PROTECT YOUR PERSONAL + DATA AS SET FORTH IN THE PCI’S PRIVACY POLICY, PCI ASSUMES NO RESPONSIBILITY FOR THE + TIMELINESS, DELETION, MIS-DELIVERY OR FAILURE TO STORE ANY CONTENT (INCLUDING YOUR + LOCATION HISTORY), USER COMMUNICATIONS OR PERSONALIZATION SETTINGS. + +
      4. Basis of the Bargain. THE LIMITATIONS OF DAMAGES SET FORTH ABOVE + ARE FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN PCI AND YOU. +
      5. +
      + +
    10. REMEDIES. +
        + +
      1. + Violations. If PCI becomes aware of any possible violations by you of + this Agreement, PCI reserves the right to investigate such violations. If, as a result + of the investigation, PCI believes that criminal activity has occurred, PCI reserves + the right to refer the matter to, and to cooperate with, any and all applicable legal + authorities. PCI is entitled, except to the extent prohibited by applicable law, to + disclose any information or materials you provide to PCI in connection with your use + of the Application, to (a) comply with applicable laws, legal process or governmental + request; (b) enforce these Terms of Use, (c) respond to your requests for customer + service, or (d) protect the rights, property or personal safety of PCI or the public, + and all enforcement or other government officials, as PCI, in its sole discretion + believes to be necessary or appropriate. +
      2. +
      + +
    11. TERM AND TERMINATION. +
        + +
      1. + Term. This Agreement commences on the date when you accept them (as + described in the preamble above) and remain in full force and effect while you use the + Application, unless terminated earlier in accordance with this Agreement. + +
      2. Prior Use. Notwithstanding the foregoing, you hereby acknowledge + and agree that this Agreement commenced on the earlier to occur of (a) the date you + first used the Application or (b) the date you accepted this Agreement and will remain + in full force and effect while you use the Application, unless earlier terminated in + accordance with this Agreement. + +
      3. Termination by You. If you want to terminate this Agreement, you + may do so by deleting the Application from your mobile device. + +
      4. Effect of Termination. Termination of this Agreement requires you + to delete the Application and cease all use of it. All provisions of this Agreement + which by their nature should survive, shall survive termination, including without + limitation, ownership provisions, warranty disclaimers, and limitation of liability. +
      5. +
      + +
    12. INTERNATIONAL USERS. The Application is intended solely for use in the + United States of America. PCI makes no representations that the Application is functional + in other locations. Those who access or use the Application from other countries do so at + their own risk and are responsible for use in compliance with local law. + +
    13. DISPUTE RESOLUTION. Please read the following arbitration agreement in this + Section (“Arbitration Agreement”) carefully. It requires U.S. users to + arbitrate disputes with PCI and limits the manner in which you can seek relief from + us. +
        + +
      1. + Applicability of Arbitration Agreement. You agree that any + dispute, claim, or request for relief relating in any way to your access or use of the + Application or to any aspect of your relationship with PCI, will be resolved by + binding arbitration, rather than in court, except that (1) you may assert claims or + seek relief in small claims court if your claims qualify,; and (2) you or PCI may seek + equitable relief in court for infringement or other misuse of intellectual property + rights (such as trademarks, trade dress, domain names, trade secrets, copyrights, and + patents). This Arbitration Agreement shall apply, without limitation, to all + disputes or claims and requests for relief that arose or were asserted before the + effective date of this Agreement or any prior version of this Agreement. + +
      2. Arbitration Rules and Forum. The Federal Arbitration Act + governs the interpretation and enforcement of this Arbitration Agreement. To begin an + arbitration proceeding, you must send a letter requesting arbitration and describing + your dispute or claim or request for relief to our registered agent Samuel Hoff c/o + Pierce & Mandell, P.C. 11 Beacon Street Suite 800, Boston MA 02108. The arbitration + will be conducted by JAMS, an established alternative dispute resolution + provider. Disputes involving claims, counterclaims, or request for relief under + $250,000, not inclusive of attorneys’ fees and interest, shall be subject to JAMS’s + most current version of the Streamlined Arbitration Rules and procedures available at + http://www.jamsadr.com/rules-streamlined-arbitration/; all other disputes shall be + subject to JAMS’s most current version of the Comprehensive Arbitration Rules and + Procedures, available at http://www.jamsadr.com/rules-comprehensive-arbitration/. + JAMS’s rules are also available at www.jamsadr.com or by calling JAMS at 800-352-5267. + If JAMS is not available to arbitrate, the parties will select an alternative arbitral + forum. If the arbitrator finds that you cannot afford to pay JAMS’s filing, + administrative, hearing and/or other fees and cannot obtain a waiver from JAMS, PCI + will pay them for you. In addition, PCI will reimburse all such JAMS’s filing, + administrative, hearing and/or other fees for disputes, claims, or requests for relief + totaling less than $10,000 unless the arbitrator determines the claims are frivolous. +

        + You may choose to have the arbitration conducted by telephone, based on written + submissions, or at another mutually agreed location. Any judgment on the award + rendered by the arbitrator may be entered in any court of competent jurisdiction. +

        +
          + +
        1. + Authority of Arbitrator. The arbitrator shall have exclusive + authority to (a) determine the scope and enforceability of this Arbitration + Agreement and (b) resolve any dispute related to the interpretation, + applicability, enforceability or formation of this Arbitration Agreement + including, but not limited to, any assertion that all or any part of this + Arbitration Agreement is void or voidable. The arbitration will decide the rights + and liabilities, if any, of you and PCI. The arbitration proceeding will not be + consolidated with any other matters or joined with any other cases or parties. + The arbitrator shall have the authority to grant motions dispositive of all or + part of any claim. The arbitrator shall have the authority to award monetary + damages and to grant any non-monetary remedy or relief available to an individual + under applicable law, the arbitral forum’s rules, and this Agreement (including + the Arbitration Agreement). The arbitrator shall issue a written award and + statement of decision describing the essential findings and conclusions on which + the award is based, including the calculation of any damages awarded. The + arbitrator has the same authority to award relief on an individual basis that a + judge in a court of law would have. The award of the arbitrator is final and + binding upon you and us. + +
        2. Waiver of Jury Trial. YOU AND PCI HEREBY WAIVE ANY + CONSTITUTIONAL AND STATUTORY RIGHTS TO SUE IN COURT AND HAVE A TRIAL IN FRONT OF A + JUDGE OR A JURY. You and PCI are instead electing that all disputes, claims, or + requests for relief shall be resolved by arbitration under this Arbitration + Agreement, except as specified in Section 13.1 above. An arbitrator can award on + an individual basis the same damages and relief as a court and must follow this + Agreement as a court would. However, there is no judge or jury in arbitration, and + court review of an arbitration award is subject to very limited review. + +
        3. Waiver of Class or Other Non-Individualized Relief. ALL + DISPUTES, CLAIMS, AND REQUESTS FOR RELIEF WITHIN THE SCOPE OF THIS ARBITRATION + AGREEMENT MUST BE ARBITRATED ON AN INDIVIDUAL BASIS AND NOT ON A CLASS OR + COLLECTIVE BASIS, ONLY INDIVIDUAL RELIEF IS AVAILABLE, AND CLAIMS OF MORE THAN ONE + CUSTOMER OR USER CANNOT BE ARBITRATED OR CONSOLIDATED WITH THOSE OF ANY OTHER + CUSTOMER OR USER. If a decision is issued stating that applicable law precludes + enforcement of any of this subsection’s limitations as to a given dispute, claim, + or request for relief, then such aspect must be severed from the arbitration and + brought into the State or Federal Courts located in the Commonwealth of + Massachusetts. All other disputes, claims, or requests for relief shall be + arbitrated. + +
        4. 30-Day Right to Opt Out. You have the right to opt out of the + provisions of this Arbitration Agreement by sending written notice of your + decision to opt out to: legal@pathcheck.org, within 30 days after first becoming + subject to this Arbitration Agreement. Your notice must include your name and + address, your email address, and an unequivocal statement that you want to opt out + of this Arbitration Agreement. If you opt out of this Arbitration Agreement, all + other parts of this Agreement will continue to apply to you. Opting out of this + Arbitration Agreement has no effect on any other arbitration agreements that you + may currently have, or may enter in the future, with us. + +
        5. Severability. Except as provided in subsection 13.5, + if any part or parts of this Arbitration Agreement are found under the law to be + invalid or unenforceable, then such specific part or parts shall be of no force + and effect and shall be severed and the remainder of the Arbitration Agreement + shall continue in full force and effect. + +
        6. Survival of Agreement. This Arbitration Agreement will + survive the termination of your relationship with PCI. + +
        7. Modification. Notwithstanding any provision in + this Agreement to the contrary, we agree that if PCI makes any future material + change to this Arbitration Agreement, you may reject that change within thirty + (30) days of such change becoming effective by writing PCI at the following + address: Path Check, Inc. PO Box 441621, Somerville MA 02144 +
        8. +
        + +
      3. GENERAL PROVISIONS. +
          + +
        1. + Electronic Communications. The communications between you and PCI + may take place via electronic means, whether you send PCI e-mails, or whether PCI + posts notices in the Application or through updates made to the Application in + accordance with this Terms of Use or communicates with you via e-mail. For + contractual purposes, you (a) consent to receive communications from PCI in an + electronic form; and (b) agree that all terms and conditions, agreements, notices, + disclosures, and other communications that PCI provides to you electronically + satisfy any legal requirement that such communications would satisfy if it were to + be in writing. The foregoing does not affect your statutory rights, including but + not limited to the Electronic Signatures in Global and National Commerce Act at 15 + U.S.C. §7001 et seq. (“E-Sign”). + +
        2. Release. You hereby release the PCI Parties and their + successors from claims, demands, any and all losses, damages, rights, and actions + of any kind, including personal injuries, death, and property damage, that is + either directly or indirectly related to or arises from any interactions with or + conduct of operators of Third Party Safe Places Web Apps, healthcare providers, + governmental agencies, of any kind arising in connection with or as a result of + this Agreement or your use of the Application. If you are a California resident, + you hereby waive California Civil Code Section 1542, which states, “A general + release does not extend to claims that the creditor or releasing party does not + know or suspect to exist in his or her favor at the time of executing the release + and that, if known by him or her, would have materially affected his or her + settlement with the debtor or released party.” The foregoing release does not + apply to any claims, demands, or any losses, damages, rights and actions of any + kind, including personal injuries, death or property damage for any unconscionable + commercial practice by a PCI Party or for such party’s fraud, deception, false, + promise, misrepresentation or concealment, suppression or omission of any material + fact in connection with the Application provided hereunder. + +
        3. Assignment. This Agreement, and your rights and obligations + hereunder, may not be assigned, subcontracted, delegated or otherwise transferred + by you without PCI’s prior written consent, and any attempted assignment, + subcontract, delegation, or transfer in violation of the foregoing will be null + and void. + +
        4. Force Majeure. PCI shall not be liable for any delay or failure + to perform resulting from causes outside its reasonable control, including, but + not limited to, acts of God, war, terrorism, riots, embargos, acts of civil or + military authorities, fire, floods, accidents, strikes or shortages of + transportation facilities, fuel, energy, labor or materials. + +
        5. Questions, Complaints, Claims. If you have any questions, + complaints or claims with respect to the Application, please contact us at: + support@pathcheck.org. + +
        6. Exclusive Venue. To the extent the parties are permitted under + this Agreement to initiate litigation in a court, both you and PCI agree that all + claims and disputes arising out of or relating to this Agreement will be litigated + exclusively in the state or federal courts located in Boston, Massachusetts. + +
        7. Governing Law THE TERMS AND ANY ACTION RELATED THERETO WILL BE + GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF + MASSACHUSETTS, CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT + TO ANY PRINCIPLES THAT PROVIDE FOR THE APPLICATION OF THE LAW OF ANOTHER + JURISDICTION. THE UNITED NATIONS CONVENTION ON CONTRACTS FOR THE INTERNATIONAL + SALE OF GOODS DOES NOT APPLY TO THIS AGREEMENT. + +
        8. Choice of Language. It is the express wish of the parties that + this Agreement and all related documents have been drawn up in English. + +
        9. Notice. You may give notice to PCI at the following address: + Path Check, Inc. PO Box 441621, Somerville MA 02144. Such notice shall be deemed + given when received by PCI by letter delivered by nationally recognized overnight + delivery service or first class postage prepaid mail at the above address. + +
        10. Waiver. Any waiver or failure to enforce any provision of this + Agreement on one occasion will not be deemed a waiver of any other provision or of + such provision on any other occasion. + +
        11. Severability. If any portion of this Agreement is held invalid + or unenforceable, that portion shall be construed in a manner to reflect, as + nearly as possible, the original intention of the parties, and the remaining + portions shall remain in full force and effect. + +
        12. Export Control. You may not use, export, import, or transfer + the Application except as authorized by U.S. law, the laws of the jurisdiction in + which you obtained PCI Properties, and any other applicable laws. In particular, + but without limitation, the Application may not be exported or re-exported (a) + into any United States embargoed countries, or (b) to anyone on the U.S. Treasury + Department’s list of Specially Designated Nationals or the U.S. Department of + Commerce’s Denied Person’s List or Entity List. By using the Application, you + represent and warrant that (y) you are not located in a country that is subject to + a U.S. Government embargo, or that has been designated by the U.S. Government as a + “terrorist supporting” country and (z) you are not listed on any U.S. Government + list of prohibited or restricted parties. You also will not use the Application + for any purpose prohibited by U.S. law, including the development, design, + manufacture or production of missiles, nuclear, chemical or biological weapons. + You acknowledge and agree that products, services or technology provided by PCI + are subject to the export control laws and regulations of the United States. You + shall comply with these laws and regulations and shall not, without prior U.S. + government authorization, export, re-export, or transfer PCI products, services or + technology, either directly or indirectly, to any country in violation of such + laws and regulations. + +
        13. Accessing and Downloading the Application from iTunes. The + following applies to any App Store Sourced Application accessed through or + downloaded from the Apple App Store: +
            + +
          1. + You acknowledge and agree that (i) this Agreement is concluded between you and + PCI only, and not Apple, and (ii) PCI, not Apple, is solely responsible for + the App Store Sourced Application and content thereof. Your use of the App + Store Sourced Application must comply with the App Store Terms of Service. + +
          2. You acknowledge that Apple has no obligation whatsoever to furnish any + maintenance and support services with respect to the App Store Sourced + Application. + +
          3. In the event of any failure of the App Store Sourced Application to conform + to any applicable warranty, you may notify Apple, and Apple will refund the + purchase price for the App Store Sourced Application to you and to the maximum + extent permitted by applicable law, Apple will have no other warranty + obligation whatsoever with respect to the App Store Sourced Application. As + between PCI and Apple, any other claims, losses, liabilities, damages, costs + or expenses attributable to any failure to conform to any warranty will be the + sole responsibility of PCI. + +
          4. You and PCI acknowledge that, as between PCI and Apple, Apple is not + responsible for addressing any claims you have or any claims of any third + party relating to the App Store Sourced Application or your possession and use + of the App Store Sourced Application, including, but not limited to: (i) + product liability claims; (ii) any claim that the App Store Sourced + Application fails to conform to any applicable legal or regulatory + requirement; and (iii) claims arising under consumer protection or similar + legislation. + +
          5. You and PCI acknowledge that, in the event of any third-party claim that the + App Store Sourced Application or your possession and use of that App Store + Sourced Application infringes that third party’s intellectual property rights, + as between PCI and Apple, PCI, not Apple, will be solely responsible for the + investigation, defense, settlement and discharge of any such intellectual + property infringement claim to the extent required by this Agreement. + +
          6. You and PCI acknowledge and agree that Apple, and Apple’s subsidiaries, are + third-party beneficiaries of this Agreement as related to your license of the + App Store Sourced Application, and that, upon your acceptance of the terms and + conditions of this Agreement, Apple will have the right (and will be deemed to + have accepted the right) to enforce this Agreement as related to your license + of the App Store Sourced Application against you as a third-party beneficiary + thereof. + +
          7. Without limiting any other terms of this Agreement, you must comply with all + applicable third-party terms of agreement when using the App Store Sourced + Application. +
          8. +
          + +
        14. Consumer Complaints. In accordance with California Civil Code + §1789.3, you may report complaints to the Complaint Assistance Unit of the + Division of Consumer Services of the California Department of Consumer Affairs by + contacting them in writing at 1625 North Market Blvd., Suite N 112, Sacramento, CA + 95834, or by telephone at (800) 952-5210. + +
        15. Entire Agreement. This Agreement is the final, complete and + exclusive agreement of the parties with respect to the subject matter hereof and + supersedes and merges all prior discussions between the parties with respect to + such subject matter. + + + + +`; diff --git a/app/locales/eula/ht_html.js b/app/locales/eula/ht_html.js new file mode 100644 index 0000000000..d20a4bf412 --- /dev/null +++ b/app/locales/eula/ht_html.js @@ -0,0 +1,775 @@ +export default ` + + + + + + + + + + +

          + Règleman itilizasyon
          +

          +

          Dènye Mizajou Dat: 23 Avril 2020

          +

          + TANPRI LI RÈGLEMAN ITILIZASYON AKÒ SA YO ( + "KONDISYON POU ITILIZE" OU "AKÒ") AVÈK + ATANSYON. APLIKASYON MOBIL SAFE PATHS ‘CHASE KOWONA’ ("APLIKASYON" + an), AK karakteristik ak enfòmasyon ki ladanl yo ap kontwole pa Path Check, + INC. ("PCI"). Règleman itilizasyon sa yo gouvène itilizasyon aplikasyon an epi + aplike pou tout itilizatè ki itilize aplikasyon an nan nenpòt fason, ki gen + ladan karakteristik sa yo. SIW klike sou BOUTON "mwen aksepte" ak / oswa + TELECHAJE APLIKASYON an, ou repwezante ke (1) ou li, konprann, ak dakò pou yo + atache pa règleman itilizasyon yo, (2) ou gen laj legal pou fòme yon LWA, + KONTRA AK PCI, E (3) OU GEN OTORITE POU ANTRE NAN RÈGLEMAN ITILIZASYON YO. + SI OU PA AKSPTE REGLEMAN ITILIZASYON YO OU PA KA AKSEDE NI SÈVI AK + APLIKASYON AN.
          +
          +

          +

          + Tanpri pran konsyans ke SEKSYON 13 nan akò sa a, ki anba a, gen ladan + dispozisyon ki konfere ki jan depandans ke youn ak lot yo ka rezoud, ki + enkli, san limit, nenpòt dispit ki leve oswa ki te asire anvan dat efikas + nan akò sa a. An patikilye, li genyen yon akò abitraj ki prale avèk + eksepsyon limite konfli antre nou ki dwe soumèt bay lyen ak abitraj final + lan. A MWEN KE OU SOTI NAN ABITRAJ AKÒ A: (1) OU AP PÈMEN SÈLMAN POU POUSWIV + DISPIt OU REKLAMASYON AK REVIZYON KONT NOU SOU YON BAZ ENDIVIDYEL, PA KOM + YON ASIRANS OSWA YON KLAS OU AKSYON REPREZANTAN OSWA PWOSEDI; AK (2) OU ap + renonse dwa ou nan pouswiv dispit oswa reklamasyon ak chèche jwenn soulajman + nan yon tribinal lwa epi yo gen yon JIRI PWOSÈ.
          +
          +

          +

          + NENPOT DISPUTE, REKLAMASYON OU DEMANN POU RELYE RELASYON AN NENPOT MOUN KI + POU ITILIZE APLIKASYON W AP REGLE E INTERPRETE PA AK LWA NAN LWA ETA + MASSACHUSETTS, ki konsistan avèk Lwa sou Abitraj FEDERAL, SAN FONDEMAN bay + nenpòt prinsip ki bay pou APLIKASYON LWA POU TOUT LOT JURISDIKSYON an. + KONVANSYON Nasyonzini an sou KONTRA POU VANN entènasyonal la nan machandiz + yo eksprime eksprè nan akò sa a.
          +
          +

          +

          + TANPRI REMAKE KE AKÒ SA A SIJÈ POU CHANJE PA PCI NAN DISKRESYON NENPOT LÈ. Lè + chanjman yo fèt, PCI pral fè yon nouvo kopi tèm itilizasyon ki disponib nan + aplikasyon an. Nou pral tou mete ajou "Dènye Mizajou" dat la nan tèt Regleman + itilizasyon yo. Itilizasyon kontinyèl ou pou Aplikasyon an apre yon chanjman + nan Regleman itilizasyon yo konstitye yon akseptasyon. Tanpri, tcheke + APLIKASYON REGILYEMAN POU W WE KONDISYON ki pi aktyèl yo. +

          +

          + 1. ITILIZASYON APLIKASYON AN.
          + 1.1 Definisyon. Menm jan nan Règleman itilizasyon yo, + definisyon sa yo aplike: "Itilizatè Aplikasyon" vle di yon + moun natirèl moun ki te telechaje Aplikasyon sou Regleman pou Itilize yo. +

          +

          + "Lis lokalizasyon" vle di done kote ki kolekte nan aparèy + mobil yon Itilizatè Aplikasyon an pou jwenn GPS, Bluetooth ak / oswa lòt + karakteristik oswa lojisyèl, ke itilizatè a App chwazi yo te anrejistre epi + estoke nan aplikasyon an enstale sou aparèy mobil itilizatè app a. +

          +

          + "Done Piblik ki disponib kote ke sekrize" vle di kat anonim + nan kote piblik kote moun dyagnostike ak Covid-19 te vizite ak done dosye nan + lè yo te vizite kote sa yo, konpile e ki te kreye pa yon twazyèm pati, epi yo + te fè disponib atravè twazyèm pati nan twazyèm pati a Zòn Santral App entènèt. +

          +

          + "Safe Places lojisyèl" vle di louvri-sous lojisyèl, ki + disponib nan + https://github.com/tripleblindmarkets/safe-places + , ki fasilite kontakte tras ak travay ki gen rapò, epi ki fèt pou itilize pa + twazyèm pati, tankou ajans gouvènman an. +

          +

          + "Twazyèm Pati Safe Places Web App" vle di yon aplikasyon ki + baze sou wèb ki posede ak opere pa yon twazyèm pati, tankou yon ajans + gouvènman an, ki anplwaye Safe Places lojisyèl, pami lòt bagay, pibliye Done + Piblik Disponib Kote yo . +

          +

          + 1.2 Karakteristik aplikasyon ak fonksyonalite. Aplikasyon an + fèt pou pèmèt Itilizatè App, nan opsyon yo: (a) pou anrejistre lis + lokalizasyon yo epi pou konsève done sa yo lokalman nan Aplikasyon an, + telechaje nan aparèy mobil yo, (b) pou jwenn aksè nan kote ki disponib yo + piblikman nan yonn oswa plis Twazyèm Pati san Patipri Sit Entènèt Apps ak + telechaje li nan Safe Paths App yo telechaje sou aparèy mobil yo, ak (c) yo + pataje ki estoke yo lis lokalizasyon ak twazyèm pati ke yo chwazi. +

          +

          + 1.3 Lisans aplikasyon an. Aplikasyon an ak enfòmasyon an ak + kontni ki disponib ladan l 'yo pwoteje pa lwa sou copyright nan tout mond lan. + Sijè a an konfòmite avèk Akò sa a, PCI ba ou yon lisans limite ki pa exklizif, + ki pa transfere, ki pa soulisansye, revokab pou telechaje, enstale epi sèvi ak + yon kopi Aplikasyon an sou yon sèl aparèy mobil oswa òdinatè ke ou posede oswa + kontwole e itlize tankou yon kopi aplikasyon an sèlman pou pwòp zafè pèsonèl + ou oswa biznis. Sèten konpozan oswa bibliyotèk ki enkli oswa ki mete ansanm + avèk Aplikasyon an konstitye lojisyèl sous epi yo gen lisans anba lisans sous + louvri. Nan limit ki egzije lisans sous yo, kondisyon ki nan lisans sa yo ap + aplike nan plas kondisyon ki nan Seksyon sa a 1.3, sèlman ki gen rapò ak sa yo + konpozan oswa bibliyotèk ki gen lisans anba lisans sous. Pou yon kopi lisans + sous sa yo, vizite + https://github.com/tripleblindmarketc/covid-safe-paths/blob/develop/LICENSE + . Anplis de sa, ki gen rapò ak nenpòt ki aplikasyon jwenn aksè nan oswa + telechaje nan magazen an App Apple (yon + "App Store Sourced Aplikasyon"), ou pral sèlman itilize App + Store Sourcing Aplikasyon an (a) sou yon pwodwi Apple ki mak ki iOS la (Apple + la sistèm operasyon propriétaires) ak (b) jan sa pèmèt nan "lwa Règleman yo" + etabli nan kondisyon ki nan Apple la Store App. Malgre premye fraz la nan + seksyon sa a, ki gen rapò ak nenpòt ki Aplikasyon jwenn aksè a oswa telechaje + soti nan magazen an Google Play (yon + "Google Play Sourcing Aplikasyon"), ou ka gen dwa lisans + adisyonèl ki gen rapò ak itilize nan Aplikasyon an sou yon baz pataje nan + gwoup fanmi ou deziyen an. +

          +

          + 1.4 Updates. Ou konprann ke pou kenbe Aplikasyon an pi itil + pou Itilizatè App ak akomode fikse ensèk ak lòt chanjman teknolojik, PCI ka fè + mizajou nan Aplikasyon an apre ou fin telechaje Aplikasyon an. Mizajou sa yo + ap disponib pou Itilizatè App yo nan men twazyèm pati ki soti nan ki moun ou + te resevwa lisans Aplikasyon an, eg, Apple App Store oswa Google Play (chak, + yon "App Store"). Ou pral gen kapasite a, nan opsyon ou a, + download nenpòt ki dènye nouvèl sou aplikasyon an ke nou fè lib disponib nan + chanèl sa yo. Nou pa mande pou ou enstale nenpòt ki dènye enfòmasyon yo nan + lòd pou ou pou w kontinye lè l sèvi avèk Aplikasyon an apre nou fè dènye + enfòmasyon sa yo disponib. Sa a se paske nou pa kenbe okenn enfòmasyon sou ou + lè ou telechaje Aplikasyon an. Se poutèt sa, nou pa pral konnen si ou te + enstale nenpòt ki dènye nouvèl sa yo. An konsekans, ou rekonèt epi mwen dakò + ke si ou chwazi pa enstale dènye disponib, Aplikasyon an pa pouvwa opere an + akò ak dokiman piblik ki disponib konsènan karakteristik yo ak fonksyonalite + nan aplikasyon an, ki dokiman yo ka mete ajou apre tan ou te telechaje li + orijinal la. Anplis de sa, ou ka bezwen mete ajou lojisyèl twazyèm-pati de tan + zan tan yo nan lòd yo sèvi ak aplikasyon an. Nenpòt dènye emèt pa PCI ak + enstale pa ou yo dwe jije aplikasyon an epi yo dwe gouvène pa sa a Safe Places + EULA oswa nenpòt ki apre akò lisans itilizatè fen akonpaye aktyalizasyon la. +

          +

          + 1.5 Ekipman ki nesesè yo ak lojisyèl. Ou dwe bay tout ekipman + ak lojisyèl ki nesesè pou telechaje epi itilize Aplikasyon an epi pou konekte + ak sèvis oswa sit twazyèm pati, ki enkli men pa limite a, yon aparèy mobil ki + apwopriye pou konekte ak aplikasyon an. Ou se sèl responsab pou nenpòt frè, ki + gen ladan koneksyon entènèt oswa frè mobil, ke ou antrene lè aksè nan + aplikasyon an. +

          +

          + 1.6 Responsablite pou lis lokaizasyon ak Lòt Done Kolekte ak ki estoke pa + Ou. + PCI pa gen okenn aksè a Kote Istwa a oswa nenpòt ki lòt done ou kolekte, + resevwa ak magazen nan aplikasyon an enstale oswa otreman sou aparèy mobil ou. + Se poutèt sa, PCI pa gen okenn responsablite pou sipresyon an oswa presizyon + nan Istwa Kote a oswa lòt done ou kolekte, resevwa oswa magazen nan aplikasyon + an oswa echèk la nan magazen, transmèt oswa resevwa transmisyon nan Istwa Kote + oswa lòt done; oswa sekirite, konfidansyalite, depo, oswa transmisyon lòt + kominikasyon ki soti nan oswa ki enplike itilizasyon aplikasyon an. +

          +

          2. Pwofesyonèl.

          +

          + 2.1 Aplikasyon. Eksepte sa ki gen rapò ak lis lokalizasyon ou + ak lòt done ou ka kolekte, rekipere soti nan twazyèm pati ak magazen nan + aplikasyon an sou aparèy ou an, ou dakò ke PCI ak founisè li yo posede tout + dwa, tit ak enterè nan ak aplikasyon an. Ou pa pral retire, chanje oswa fènwa + nenpòt copyright, mak komèsyal, mak sèvis oswa lòt dwa propriétaires avi + enkòpore nan oswa akonpaye Aplikasyon an. +

          +

          + 2.2 Mak yo. Chase kowona ‘Safe paths’ COVID ak tout grafik ki + konekte, logo, mak sèvis ak non komès yo itilize sou oswa an koneksyon avèk + Aplikasyon an oswa an koneksyon avèk li yo se mak komèsyal yo nan psi epi yo + pa kapab itilize san pèmisyon an koneksyon avèk ou, oswa nenpòt ki + twazyèm-pati, pwodwi oswa sèvis. Lòt mak, mak sèvis ak non komès ki ka parèt + sou oswa nan aplikasyon an se pwopriyete pwopriyetè respektif yo. +

          +

          + 3. KOLEKSYON AK ITILIZASYON ENFOMASYON PÈSONÈL OU. Ou rekonèt + ke aplikasyon an ka kolekte enfòmasyon pèsonèl sou ou (nan chwa ou nan magazen + enfòmasyon nan aplikasyon an oswa atravè zouti teknoloji otomatik), ki gen + ladan Istwa Kote ou. Ou rekonèt ke aplikasyon an ka ba ou opòtinite pou pataje + enfòmasyon pèsonèl sou tèt ou, ki gen ladan Istwa Kote ou ak lòt moun. Tout + enfòmasyon pèsonèl an koneksyon avèk aplikasyon sa a sijè a règleman sou + enfòmasyon prive nou an. Pa Téléchargez, enstale, lè l sèvi avèk, ak bay + enfòmasyon pèsonèl nan oswa atravè aplikasyon sa a, ou bay konsantman pou tout + aksyon nou pran ki gen rapò ak enfòmasyon pèsonèl ou an konfòmite ak Règleman + sou enfòmasyon prive. Pou kèk karakteristik nan aplikasyon an, konsantman + espesifik ki nesesè. Anplis de sa, ou ka chwazi divilge, nan lòt mwayen ki pa + asosye avèk Aplikasyon an, nenpòt ki pati nan enfòmasyon pèsonèl ou a manm + fanmi, doktè, founisè swen sante, ajans gouvènman, oswa lòt moun oswa antite. + Nou rekòmande ke ou fè chwa ou konsènan pataje enfòmasyon pèsonèl ou, nan + aplikasyon an oswa otreman, ak anpil atansyon. Nou pa pral gen okenn + responsablite pou nenpòt konsekans ki ka lakòz paske ou te lage oswa pataje + enfòmasyon, nan Aplikasyon an oswa otreman, ak yon twazyèm pati. +

          +

          + 4. REAKSYON. Si ou bay psi ak nenpòt ide, sijesyon, dokiman, + ak / oswa pwopozisyon ("Feedback") via imel oswa yon lòt vle + di, ou fè sa sou pwòp risk ou, epi ou rekonèt epi mwen dakò ke PCI pa gen + okenn obligasyon (ki gen ladan san obligasyon limite nan ... konfidansyalite) + ki gen rapò ak sa yo Feedback. Ou reprezante ak garanti ke ou gen tout dwa + nesesè yo soumèt Feedback la. Se konsa, ou bay yon PCI yon konplètman peye, + wayote-gratis, tout tan, ki paka chanje, atravè lemond, ki pa san konte, ak + konplètman sublicensable dwa ak lisans yo sèvi ak, repwodui, fè, montre, + distribye, adapte, modifye, re-fòma, kreye derive travay nan, ak otreman + komèsyal oswa ki pa komèsyal esplwate nan nenpòt fason, nenpòt ak tout + Feedback, ak sublisans dwa ki ekri pi wo yo, an koneksyon avèk operasyon an ak + antretyen nan aplikasyon an, nenpòt ki lòt pwodwi psi oswa sèvis, oswa biznis + pci la. +

          +

          + 5. Magazen APP. Ou rekonèt epi mwen dakò ke disponiblite + Aplikasyon an depann de magazen App a ki moun ou te resevwa lisans aplikasyon + an. Ou rekonèt ke Akò sa a se ant oumenm ak PCI epi yo pa avèk App Store la. + Psi, pa App Store la, se sèl responsab pou Aplikasyon an, ki gen ladan kontni + an psi ki disponib ladan l ', ak antretyen, sèvis sipò yo, ak garanti pou sa, + ak adrese nenpòt reklamasyon ki gen rapò ak sa yo (egzanp, responsablite + pwodwi, konfòmite legal oswa pwopriyete entelektyèl. vyolasyon). Yo nan lòd yo + itilize aplikasyon an, ou dwe gen aksè a yon rezo san fil, epi ou dakò yo peye + tout frè ki asosye ak aksè sa yo. Ou dakò tou pou peye tout frè yo (si genyen) + ki akize nan magazen app a an koneksyon avèk aplikasyon an. Ou dakò konfòme ou + avèk, ak lisans ou yo sèvi ak aplikasyon an se kondisyone sou konfòmite ou + avèk tout kondisyon nan akò enpoze pa App Store aplikab la lè w ap itilize + aplikasyon an. Ou rekonèt ke magazen an App (ak filiales li yo) yo se + benefisyè twazyèm pati nan akò sa a epi yo pral gen dwa aplike li. +

          +

          + 6. FRÈ. Pa gen frè yo dwe peyab anba akò sa a pou dwa yo + akòde anba akò sa a. Ou rekonèt epi mwen dakò ke aranjman frè sa a fèt an + konsiderasyon pou alyans mityèl ki tabli nan Akò sa a, ki gen ladan obligasyon + ou genyen anba la a, ak avètisman yo, esklizyon yo, ak limit responsablite ki + prezante nan dokiman sa a. +

          +

          + 7. ENDEMNIFICASYON. Ou dakò dedomaje epi kenbe psi, paran li + yo, filiales, afilye, ofisye, anplwaye, volontè, ajan, patnè, founisè, ak + lisans (chak, yon "PCI Pati" ak kolektivman, "PCI Pati yo") inonsan nan nenpòt + ki pèt. , depans, dèt ak depans (ki gen ladan frè avoka rezonab) ki gen rapò + ak oswa ki rive soti nan nenpòt ak tout bagay sa yo: (a) itilize ou nan, oswa + enkapasite yo sèvi ak aplikasyon an; (b) vyolasyon ou nan Kontra sa-a; (c) + vyolasyon ou nan nenpòt ki dwa nan yon lòt pati; oswa (d) vyolasyon ou nan + nenpòt lwa, règleman oswa règleman ki aplikab yo. Psi rezève dwa pou, nan pwòp + depans li yo, pou asime defans san konte ak kontwòl sou nenpòt pwoblèm otreman + sijè a dediksyon pa ou, nan ki evènman ou pral konplètman kolabore ak psi nan + revandike nenpòt defans ki disponib. Dispozisyon sa a pa egzije ou dedomaje + nenpòt nan Pati yo PCI pou nenpòt ki pratik komèsyal ki pa abizyon pa pati sa + a oswa pou fwod pati sa a, desepsyon, fo pwomès, bay manti oswa kache, + repwesyon oswa omisyon nan nenpòt ki reyalite materyèl an koneksyon avèk + aplikasyon yo bay la anba. . Ou dakò ke dispozisyon ki nan seksyon sa a ap + siviv nenpòt ki revokasyon akò sa a ak / oswa ou itilize oubyen ou aksede a + aplikasyon an. +

          +

          8. Esklizyon garanti ak kondisyon

          +

          + 8.1 Konsa. OU DWE KONPRANN AK dakò POU PWOBLÈM APLIKAB, + APLIKASYON OU NAN APLIKASYON OU SE YO NAN RISK SÈL OU, AK APLIKASYON YO + DISPONTE NAN YO "jan sa ye" ak "jan sa disponib", avèk tout peche. Pati ki PCI + EXPRESSLY DISI RESPONSABLOU tout garanti, REPREZANTASYON, ak kondisyon nan + nenpòt kalite, menm si yo eksprime oswa enplike, ki gen ladan, men PA limite + a, GARANTI IMPLIYE OSWA kondisyon yo ki nan komès, fòm pou yon bi patikilye ak + ki pa vyolasyon ki soti nan itilizasyon APLIKASYON. +

          +

          + (a) PATI yo PCI pa fè okenn garanti, reprezantasyon oswa + kondisyon ke: (1) APLIKASYON la oswa karakteristik li yo ap satisfè egzijans + ou; (2) ITILIZASYON OU NAN APLIKASYON AN YO PAP KONTINYE, PWOPRIYE, SEKIRITE + OUBYE ERÈ GRATIS; OSWA (3) REZILTA YO KI JWENN PWOCHE NAN ITILIZASYON + APLIKASYON AN YO KAP FÈ OU FYAB. +

          +

          + (b) Nenpòt kontni ki soti nan oswa ki aksesib pa mwayen + aplikasyon an aksè nan pèt pwòp ou a, epi ou dwe toujou fè responsablite pou + nenpòt ki domaj nan pwopriyete w, ki gen ladan, men li pa limite a, sistèm + konpitè ou ak nenpòt aparèy ou sèvi pou jwenn aksè nan aplikasyon an, OSWA + NENPOT L THT PÈDI KI REZILTAJ Soti nan aksè nan kontni sa yo. +

          +

          + (c) Disponibilite pou aplikasyon an ak karakteristik li yo ka + sijè a reta, anilasyon ak lòt pyès lajan. Psi pa fè okenn garanti, + REPREZANTASYON oswa kondisyon sou respè APLIKASYON an oswa karakteristik li + yo, ki gen ladan men li pa limite a, kalite, efikasite, repitasyon ak lòt + karakteristik sa yo. +

          +

          + (d) Pa gen konsèy oswa ENF ,MASYON, kèlkeswa ORAL oswa EKRI, + jwenn soti nan psi oswa atravè aplikasyon an pral kreye nenpòt GARANTI pa + eksprime fè HEREIN. +

          +

          + (e) Soti nan tan pou tan, PCI ka ofri nouvo "BETA" + karakteristik oswa zouti ak ki moun ki itilizatè li yo ka eksperyans. + Karakteristik sa yo oswa zouti yo ofri sèlman pou objektif eksperimantal ak + san okenn garanti nenpòt ki kalite, e yo ka modifye oswa rete nan DISKRÈS SÈL + PCI a. DISPOZISYON YO SEKSYON SA A APLIKE AK TOUT FS POU Karakteristik sa yo + oswa zouti. +

          +

          + 8.2 PA ENPTE POU KONSÈY MEDIKAL. Ou rekonesan ke ENFORMASYON + SOU APLIKASYON AN FÈ POU FÈ YO POU FINANS ENFOMASYON JENERAL SÈLMAN. Li pa + konsantre kòm yon konsèy MEDIKAL sou nenpòt ki kalite TIMOUN OSYO li se + entansyon DYAGNOSE, trete, geri oswa anpeche okenn maladi oswa kondisyon + medikal. ENFMASYON SOU PREZIDAN NAN APLIKASYON AN PA DWE INTERPRETE OUBYEN + KONSTWI NAN NENPT fason ke yon ranplasman oswa yon sèvis konsèy medikal ki + founi pa doktè w oswa lòt founisè swen sante ki kalifye. OU PA DWE DISREGARDE, + EVITE OUBYEN RETE NAN OBTIEN MEDIKAL KONSÈY OSWA TRETMAN SOU DOKTÈ OU OUBYEN L + QUT KALIFYE HEALTHCARE PROVIDER POU TOUT ENFMASYON SOU APLIKASYON. SOU pa gen + sikonstans ou ta dwe chanje tretman medikal egziste ou, medikaman REGIMEN, + oswa nenpòt ki lòt aktivite sante ki gen rapò ki baze sou nenpòt ki enfòmasyon + ki bay nan aplikasyon an. Li ENP FORTAN POU OU DISKITE OPSYON TRETMAN OU, AK + NENPS KESYON KI OU GENYEN, AVÈK DOKTÈ OUBYEN OUBYEN L QUT LWA SOU KALIFYE + SANTE. +

          +

          + 8.3 Nan fè aplikasyon an ki disponib pou download, objektif + PCI a se bay moun ki gen yon zouti itil pou kontakte tras. Sepandan, sèvis + piblik la nan karakteristik Aplikasyon an se depann sou yon kantite faktè ki + deyò kontwòl la psi, tankou fyab la nan GPS detèktè, si wi ou non moun yo ap + pote aparèy mobil yo, menm jan tou presizyon, disponiblite, disponiblite, + efikasite. oswa itilize kòrèk la nan aparèy mobil moun nan ak GPS detèktè, + tout nan yo ki yo te itilize yo kreye Istwa Kote, ak pou nenpòt ki Piblikman + disponib Kote kote Done ke ou ka Upload nan aplikasyon ou soti nan sous + twazyèm pati, presizyon an ak konplè nan done sa yo. Istwa Kote ou oswa lòt + done ka disponib, kòrèk oswa enkonplè. Sèvi ak aplikasyon an pa ta dwe + ranplase bon jijman ou ak sans komen. +

          +

          + 8.4 Pa gen Responsablite pou Konduit nan twazyèm pati yo. Ou + rekonesan epi aksepte ke pati PCI yo pa responsab, epi ou dakò pa chèche kenbe + Psi pati yo responsab, pou kondwit la oswa omisyon nan twazyèm pati, ki gen + ladan aksyon yo ki nan nenpòt ki twazyèm pati opere yon sit entènèt kote ki + gen twazyèm pati oswa nenpòt gouvènman an. AJANS AVÈK OU CHWAZI POU INTERRÈT + SOU ITILIZASYON AK ITILIZASYON OU NAN APLIKASYON OU, AK KI RISK BAGAY KI SOTI + NAN TÈS PARTI YO, RETE AK OU. +

          +

          9. LIMITASYON RESPONSABLITE.

          +

          + 1.1 Limit responsabilite nou de sèten domaj. OU KONPRANN AK + dakò KI PA GEN EVÈNMAN PATI pati PCI YON RESPONSAB POU NENPT PÈS POU PROFIT, + BLIJI PÈSONÈL, DANJE PWOPRIYETE, MOVE MALADI, ENFMASYON OSWA DONE, DANI + INDIRÈT, ENSIDANS, ESPESYAL, OSWA DWA, OSWA DANYE oswa domaj OSWA KOTE POU + Pèdi yo. PWODIKSYON OSWA ITILIZASYON, ENTRETISYON BIZNIS, KONTAKTE OU SÈVIS SE + YON SÈVIS, NAN CHAK KASYE OU SÈLMAN PCI YO te AVÈVRE POU POSIBILITE DAN, + KONTINYE NAN OU KONNEKSYON AVÈK AKANSYON, NAN NENPT TEWO KI RESPONSABILITE, + REZILTE Soti nan : (1) itilizasyon an oswa inabilite pou itilize aplikasyon an + oswa nenpòt nan karakteristik li yo piblisite; (2) AKSE NAN OTORIZE POU OU + oswa modifikasyon nan transmisyon OU oswa DONE; OUBYEN (3) NENPT LT KESYON KI + KONSÈNE APLIKASYON AN, OU SOU BAZE SOU GARANTI, DWA KONTRA, KONTRA, TRIBE (KI + GEN NÉLIGENCE), OSWA NENPT L .T TEORI LEGAL. NAN PWOCHEN AN PREZAN SOU + RESPONSABLITE PAP APLIKE POU RESPONSABLITE YON PAPI PAPI POU (A) LANM OSWA + KIJE PÈSONÈL KI FÈ PANDAN PANDAN PATI yon pati PIKI; OSWA POU (B) Nenpòt blesi + ki te koze pa yon fwis PCI pati a oswa yon fo reprezantasyon FRAUDULENT. +

          +

          + 1.2 Kap sou Responsabilite. POU PWOJÈ maksimòm PEMISYE POU + LWA, KI KOTE POU NENPAY NENPOT POU KONTW CONCH KI GENYEN LA, RESPONSABLITE NOU + POU OU pou nenpòt domaj ki soti nan oswa ki gen rapò ak kondisyon sa yo pou + itilize (pou nenpòt ki koz toutbon menm jan ak fòm nan AKSYON an), yo pral nan + tout. Kantite tan yo limite a yon maksimòm de senk DOLARS US (US $ 50). + Egzistans plis pase yon sèl reklamasyon p ap elaji limit sa a. Ou dakò ke + founisè nou yo p ap gen okenn responsablite nenpòt ki kalite ki sòti nan oswa + ki gen rapò ak kondisyon sa yo sèvi ak. +

          +

          + 1.3 Done ou. EKSEPTE pou OBLIGASYON PCI a PWOTEJE DONE + PÈSONÈL OU jan li te etabli nan règleman prive PCI a, PCI sipoze pa gen okenn + responsablite pou tan an, DELETION, MIS-livrezon oswa echèk magazen nenpòt ki + kontni (ki enkli istwa lokasyon ou), itilizatè kominikasyon oswa anviwònman + pèsonalizasyon. +

          +

          + 1.4 Baz negosyasyon an. Limit nan domaj ki etabli anwo yo se + eleman fondamantal de baz la nan PAPA ant PCI ak ou. +

          +

          2. KONSÈY.

          +

          + 2.1 Vyolasyon. Si PCI vin okouran nenpòt vyolasyon posib ke + ou nan Akò sa a, PCI rezève dwa pou envestige vyolasyon sa yo. Si, kòm yon + rezilta nan ankèt la, psi kwè ke gen aktivite kriminèl ki te fèt, PCI rezève + dwa pou refere ka a, epi pou kolabore ak, nenpòt ak tout otorite legal ki + aplikab yo. PCI gen dwa, eksepte nan limit ki entèdi nan lalwa aplikab, + divilge nenpòt enfòmasyon oswa materyèl ou bay PCI an koneksyon avèk + itilizasyon ou nan Aplikasyon an, nan (a) konfòme ou avèk lwa aplikab, + pwosesis legal oswa demann gouvènman an; (B) ranfòse sa yo Regleman pou + Itilize, (c) reponn a demann ou an pou sèvis kliyan, oswa (d) pwoteje dwa yo, + pwopriyete oswa sekirite pèsonèl nan psi oswa piblik la, ak tout ranfòsman + oswa lòt ofisyèl gouvènman yo, kòm psi, nan. sèl diskresyon li kwè ke li + nesesè oswa apwopriye. +

          +

          3. Regleman ak sispansyon.

          +

          + 3.1 Regleman. Akò sa a kòmanse nan dat la lè ou aksepte yo + (jan sa dekri nan pre-an pi wo a) epi rete nan tout fòs ak efè pandan w ap + itilize aplikasyon an, sòf si sispann pi bonè nan akò ak sa a Akò. +

          +

          + 3.2 Itilize Anvan. Malgre sa ki endike anwo la a, ou rekonèt + epi mwen dakò ke Akò sa a te kòmanse nan pi bonè pou rive nan (a) dat ou te + itilize Aplikasyon an oswa (b) dat ou te aksepte Kontra sa a epi w ap rete nan + tout fòs ou pandan wap itilize. Aplikasyon an, sòf si li te sispann pi bonè + dapre Akò sa a. +

          +

          + 3.3 Revokasyon ou. Si ou vle mete fen nan Akò sa a, ou ka fè + sa pa efase aplikasyon an soti nan aparèy mobil ou. +

          +

          + 3.4 Efè Termination. Fen nan akò sa a egzije pou ou efase + aplikasyon an ak sispann tout itilize nan li. Tout dispozisyon ki nan akò sa a + ki pa nati yo ta dwe siviv, va siviv mete fen, ki gen ladan san limit, + dispozisyon pwopriyetè, avètisman garanti, ak limitasyon de responsablite. +

          +

          + 4. Itilizatè entènasyonal yo. Aplikasyon an fèt sèlman pou + itilize nan Etazini nan Amerik la. Psi pa fè okenn reprezantasyon ki + aplikasyon an fonksyonèl nan lòt kote. Moun ki gen aksè oswa ki itilize + aplikasyon an nan lòt peyi yo fè sa sou pwòp risk yo epi yo responsab pou + itilize an konfòmite ak lwa lokal yo. +

          +

          + 5. Rezolisyon dispit. Tanpri li akò arbitraj sa a nan Seksyon + sa a ("Akò Abitraj") avèk anpil atansyon. Li mande pou itilizatè Ameriken + abitraj diskisyon avèk psi epi limite fason ou ka chèche sekou nan men nou. +

          +

          + 5.1 Aplikablite nan Abitraj Akò. Ou dakò ke nenpòt dispit, + reklamasyon, oswa demann pou soulajman ki gen rapò nan nenpòt fason pou aksè + ou oswa pou sèvi ak aplikasyon an oswa nan nenpòt ki aspè nan relasyon ou ak + psi, yo pral rezoud pa abitraj obligatwa, olye ke nan tribinal, eksepte ke (1 + ) ou ka revandike reklamasyon oswa chèche sekou nan tribinal ti reklamasyon si + reklamasyon ou kalifye; epi (2) ou oswa PCI ka chèche soulajman ekitab nan + tribinal pou vyolasyon oswa lòt move itilizasyon nan dwa pwopriyete + entelektyèl (tankou mak, rad komès, non domèn, sekrè komès, copyright, ak + rive). Akò sou Abitraj sa a dwe aplike, san limitasyon, nan tout diskisyon + oswa reklamasyon ak demann pou soulajman ki leve oswa yo te pwoklame anvan dat + efektif akò sa a oswa nenpòt vèsyon anvan akò sa a. +

          +

          + 5.2 Règ Abitraj ak Forum. Lwa sou Abitraj Federal la gouvène + entèpretasyon ak ranfòsman akò sa a sou Abitraj. Pou kòmanse yon pwosedi + abitraj, ou dwe voye yon lèt pou mande abitraj epi dekri konfli ou oswa + reklamasyon ou oswa demann soulajman pou ajan ki anrejistre nou an, Samuel + Hoff c / o Pierce & Mandell, P.C. 11 Beacon Street Suite 800, Boston MA + 02108. JAMS, yon founisè etabli altènatif pou rezoud dispit la, ap fè abitraj + la. Konfli ki konsène reklamasyon, rekou, oswa demann pou soulajman ki poko + gen $ 250,000, ki pa enklizif nan avoka ak frè ak enterè, va sijè a vèsyon + JAMS ki pi resan nan Règ akize Abitraj ak pwosedi ki disponib nan + http://www.jamsadr.com/rules -abri-abinis /; tout lòt diskisyon yo pral sijè a + JAMS a vèsyon ki pi aktyèl la nan règleman yo ak pwosedi Abitraj + Comprehensive, ki disponib nan + http://www.jamsadr.com/rules-comprehensive-arbitration/. Règ JAMS a yo + disponib tou nan www.jamsadr.com oswa lè w rele JAMS nan 800-352-5267. Si JAMS + pa disponib pou arbit, pati yo ap chwazi yon fowòm altènatif arbit. Si medyatè + a jwenn ke ou pa kapab peye depozisyon JAMS yo, administratif, odyans ak / + oswa lòt frè epi yo pa ka jwenn yon egzansyon nan JAMS, PCI pral peye yo pou + ou. Anplis de sa, PCI ap ranbouse tout depozisyon JAMS a, administratif, + odyans ak / oswa lòt frè pou diskisyon, reklamasyon, oswa demann pou sekou + total yon mwens pase $ 10,000 sòf si medyatè a detèmine reklamasyon yo se + serye. +

          +

          + Ou ka chwazi fè abitasyon an fèt pa telefòn, ki baze sou soumèt alekri, oswa + nan yon lòt yo te dakò youn pou lòt. Nenpòt jijman sou prim lan bay nan + medyatè a ka antre nan nenpòt ki tribinal ki gen konpetans jiridiksyon. +

          +

          + 5.3 Otorite nan abitrè. Medyatè a dwe gen otorite eksklizif + pou (a) detèmine sijè ki abòde lan ak aplikabite nan Akò sa a Abitraj ak (b) + rezoud nenpòt dispit ki gen rapò ak entèpretasyon, aplikabilite, aplikab, oswa + fòmasyon sa a Arbitraj Akò ki gen ladan, men pa limite a, nenpòt ki afirmasyon + ki. tout oswa nenpòt ki pati nan akò sa a Abitraj anile oswa anile. Abitraj la + ap deside dwa ak responsablite, si genyen, ou menm ak PCI. Pwosedi abitraj la + pa pral konsolide ak nenpòt lòt zafè oswa ansanm ak nenpòt ki lòt ka oswa pati + yo. Medyatè a dwe gen otorite pou bay mosyon dispozisyon nan tout oswa yon + pati nan nenpòt reklamasyon. Medyatè a dwe gen otorite pou l akòde domaj + monetè ak pou bay nenpòt moun ki pa monetè remèd oswa sekou disponib nan yon + moun ki anba lwa aplikab, règleman yo forum la arbitro a, ak akò sa a (ki gen + ladan Akò a Abitraj). Medyatè a dwe bay yon prim ekri ak yon deklarasyon sou + desizyon ki dekri rezilta esansyèl yo ak konklizyon sou ki prim lan ki baze, + ki gen ladan kalkil la nan nenpòt ki domaj yo bay la. Medyatè a gen menm + otorite pou bay sekou nan yon baz endividyèl ke yon jij nan yon tribinal la ta + genyen. Prim nan medyatè a se final ak obligatwa pou ou ak pou nou. +

          +

          + 5.4 Egzanpsyon pou jijman jiri. OU AK PCI SE RENSEYE KÈK DWA + KONSTITISYONÈL AK STATWA POU VOJE NAN TRIBINAL AK YON JIJANS AN devan yon Jij + oswa yon jiri. Oumenm ak PCI se olye pou chwazi tout diskisyon, reklamasyon, + oswa demann pou soulajman yo dwe rezoud nan abitraj dapre Akò sou Abitraj sa + a, eksepte jan sa endike nan Seksyon 13.1 anwo a. Yon medyatè kapab bay yon + baz endividyèl menm domaj ak soulajman tankou yon tribinal epi li dwe swiv + Kontra sa a kòm yon tribinal ta. Sepandan, pa gen okenn jij oswa jiri nan + abitraj, ak revizyon tribinal nan yon prim abitraj se sijè a revizyon trè + limite. +

          +

          + 5.5 Egzanpsyon pou Sekou Klas oswa Lòt ki pa Peye-endividyalize. + Tout diskisyon, reklamasyon, ak Demann pou soulajman nan domèn AKIT ARBITRE sa + a dwe abrite sou yon baz endividyèl epi yo pa sou yon klas oswa baz kolektif, + SÈLMAN POU gen soulajman endividyèl, ak reklamasyon nan plis pase yon kliyan + oswa itilizatè pa ka ARBITRE Oswa konsolide ak moun ki nan nenpòt ki lòt + kliyan oswa itilizatè. Si yo bay yon desizyon ki deklare ke lwa ki aplikab + entèdi ranfòsman nan nenpòt ki limit souseksyon sa a sou yon dispit bay, + reklamasyon, oswa demann pou soulajman, Lè sa a, yo dwe aspè tankou separe nan + abitraj la ak pote nan Eta a oswa Tribinal Federal ki sitiye nan la. + Commonwealth nan Massachusetts. Tout lòt diskisyon, reklamasyon, oswa demann + pou sekou yo dwe abitraj. +

          +

          + 5.6 Dwa 30-Jou yo chwazi pou soti. Ou gen dwa pou ou pa + patisipe nan dispozisyon ki nan Kontra Abitraj sa a pa voye yon avi alekri sou + desizyon ou a patisipe soti nan: legal@pathcheck.org, nan lespas 30 jou apre + premye vin sijè a sa a Abitraj Akò. Avi ou a dwe enkli non ou ak adrès ou, + adrès imèl ou, ak yon deklarasyon inekivok ke ou vle patisipe nan Akò sa a + Abitraj. Si ou chwazi pou ou pa soti nan Abitraj sa a, tout lòt pati nan Akò + sa a ap kontinye aplike pou ou. Chwazi soti nan sa a Akò Abitraj pa gen okenn + efè sou nenpòt ki lòt akò abitraj ke ou ka kounye a gen, oswa ka antre nan nan + tan kap vini an, avèk nou. +

          +

          + 5.7 Divisibilite. Eksepte jan yo prevwa nan souseksyon 13.5, + si nenpòt pati oswa pati nan akò sa a Abitraj yo te jwenn anba lalwa a yo dwe + valab oswa ki pa ka ranfòse, Lè sa a, tankou yon pati espesifik oswa pati va + gen ki pa gen fòs ak efè epi yo pral koupe ak rès la nan Abitraj la. Akò va + kontinye nan fòs plen ak efè. +

          +

          + 5.8 Siviv nan Akò. Akò sa a Abitraj ap siviv mete fen nan + relasyon ou a ak psi. +

          +

          + 5.9 Modifikasyon. Malgre nenpòt dispozisyon ki nan Kontra + sa-a nan kontrè a, nou dakò ke si PCI fè nenpòt ki chanjman materyèl nan lavni + sa a Abitraj Akò, ou ka rejte ki chanjman nan trant (30) jou de chanjman sa yo + vin efektif pa ekri PCI nan adrès sa a: Path Check, Inc. PO Box 441621, + Somerville, MA 02144 +

          +

          + 6. DISPOZISYON JENERAL YO
          +

          +

          + 6.1 Kominikasyon elektwonik. Kominikasyon ant ou menm ak PCI + ka pran plas atravè mwayen elektwonik, si ou voye yon imèl PCI, oswa si PCI + poste avi nan Aplikasyon an oswa atravè mizajou yo te fè nan Aplikasyon an + annakò avèk sa a Regleman pou Itilize oswa kominike avèk ou via- lapòs. Pou + rezon kontra, ou (a) konsanti pou resevwa kominikasyon soti nan psi nan yon + fòm elektwonik; ak (b) dakò ke tout tèm ak kondisyon, akò, avi, divilgasyon, + ak lòt kominikasyon ke psi bay ou elektwonikman satisfè nenpòt kondisyon legal + ki kominikasyon sa yo ta satisfè si li ta dwe nan ekri. Pi wo la a pa afekte + dwa legal ou, ki enkli men pa limite a Siyati Elektwonik nan Lwa Global ak + Komès Nasyonal nan 15 US.C. §7001 et seq. ("E-Siyen"). +

          +

          + 6.2 Lansman. Ou fin divilge Pèmi yo PCI ak siksesè yo soti + nan reklamasyon, demand, nenpòt ak tout pèt, domaj, dwa, ak aksyon nenpòt + kalite, ki gen ladan blesi pèsonèl, lanmò, ak domaj pwopriyete, ki se swa + dirèkteman oswa endirèkteman ki gen rapò ak oswa rive soti nan nenpòt ki + entèraksyon ak kondwit nan operatè nan Twazyèm Pati Safe Plas Entènèt Apps, + founisè swen sante, ajans gouvènmantal, nan nenpòt kalite ki rive an koneksyon + avèk oswa kòm yon rezilta nan akò sa a oswa itilizasyon ou nan aplikasyon an. + Si ou se yon rezidan nan Kalifòni, ou renonse California Seksyon Sivil Kòd + 1542, ki deklare, "Yon lansman jeneral pa pwolonje nan reklamasyon ke + kreyansye a oswa pati lansman pa konnen oswa sispèk egziste nan favè li oswa + li nan moman sa a nan ... egzekite liberasyon an e ke, si li te konnen pa li + oswa li, li ta gen materyèl afekte l'oswa règleman li yo ak debiteur la oswa + pati lansman." Lansman ki anwo la a pa aplike pou okenn reklamasyon, demand, + oswa nenpòt pèt, domaj, dwa ak aksyon nenpòt kalite, tankou blesi pèsonèl, + lanmò oswa pwopriyete domaj pou nenpòt ki pratik komèsyal abi pou yon PCI Pati + oswa pou fwod pati sa a, desepsyon, fo, pwomès, fo reprezantasyon oswa kache, + repwesyon oswa omisyon nan nenpòt reyalite materyèl an koneksyon avèk + Aplikasyon an bay la anba a. +

          +

          + 6.3 Plasman. Akò sa a, ak dwa ou yo ak obligasyon ki anba la + a, pa ka asiyen, soutretan, delege oswa otreman transfere pa ou san ou pa + konsantman PCI anvan ekri, ak nenpòt tantativ tantasyon, tretman, delegasyon, + oswa transfè an vyolasyon sa ki ekri pi wo a pral nil epi yo anile. . +

          +

          + 6.4 Fòs majè. PCI pa dwe responsab pou okenn reta oswa echèk + nan fè rezilta ki soti nan kòz deyò kontwòl rezonab li yo, ki gen ladan, men + pa limite a, zak Bondye a, lagè, teworis, revòlt, embargos, zak otorite sivil + oswa militè, dife, inondasyon ,. aksidan, grèv oswa mank fasilite transpò, + gaz, enèji, travay oswa materyèl. +

          +

          + 6.5 Kesyon, Plent, Reklamasyon. Si ou gen nenpòt kesyon, + plent oswa reklamasyon ki gen rapò ak Aplikasyon an, tanpri kontakte nou nan: + support@pathcheck.org. +

          +

          + 6.6 Eksklizif Venue. Nan mezi pati yo gen pèmisyon dapre Akò + sa a pou kòmanse yon litij nan yon tribinal, tou de ou menm ak PCI dakò ke + tout reklamasyon ak diskisyon ki rive soti nan oswa ki gen rapò ak sa a Akò + pral plede sèlman nan tribinal yo leta oswa federal ki sitiye nan Boston, + Massachusetts. +

          +

          + 6.7 Lwa sou Gouvènman TÈM YO AK NENPT AKSYON KI KI TE RELIJE + A ANREJE AK INTERPRETE PA AK LWA NAN LWA COMMONWEALTH OF MASSACHUSETTS, ki + konsistan avèk Lwa sou Arbitraj FEDERAL, san bay efè sou nenpòt prensip ki bay + APLIKASYON LWA POU YON LDT JURISDIKSYON. KONVANSYON Nasyonzini an sou KONTRA + POU VANN entènasyonal la nan machandiz pa aplike pou akò sa a. +

          +

          + 6.8 Chwa langaj. Li se vle eksprime pati yo ke Akò sa a ak + tout dokiman ki gen rapò ak yo te trase moute nan lang angle. +

          +

          + 6.9 Avi. Ou ka remèt yon avi bay PCI nan adrès sa a: Path + Check, Inc. PO Box 441621, Somerville MA 02144. Yo dwe konsidere yon avi konsa + lè yo resevwa pa PCI pa yon lèt bay pa sèvis livrezon lannwit ke yo rekonèt + nasyonalman oswa lapòs premye pòs klas pre-peye nan nan adrès ki pi wo a. +

          +

          + 6.10 Renonsyasyon. Nenpòt egzanpsyon oswa echèk nan ranfòse + nenpòt dispozisyon nan kontra sa-a nan yon sèl okazyon pa pral jije yon + egzansyon nan nenpòt ki lòt pwovizyon oswa nan dispozisyon sa a sou nenpòt ki + lòt okazyon. +

          +

          + 6.11 Divisibilite. Si nenpòt pòsyon nan Kontra sa-a se fèt + valab oswa ki pa aplikab, yo dwe entèprete pòsyon sa a nan yon fason yo + reflete, kòm prèske ke posib, entansyon orijinal la nan pati yo, ak pòsyon ki + rete yo ap rete nan tout fòs ak efè. +

          +

          + 6.12 Ekspòtasyon kontwòl. Ou pa ka itilize, ekspòtasyon, + enpòte, oswa transfere Aplikasyon an eksepte jan otorize pa lalwa Etazini, lwa + yo nan jiridiksyon an nan ki ou te jwenn Pwopriyete PCI, ak nenpòt lòt lwa ki + aplikab yo. An patikilye, men san limit, Aplikasyon an pa ka ekspòte oswa + re-ekspòte (a) nan nenpòt peyi Etazini anbago, oswa (b) bay nenpòt moun ki sou + lis Depatman Trezò Ameriken yo ki deziyen espesyalman oswa Depatman Ameriken + an nan komès la refize. Lis Moun oswa Lis Antite yo. Lè l sèvi avèk Aplikasyon + an, ou reprezante ak garanti ke (y) ou pa sitiye nan yon peyi ki sijè a yon + anbago gouvènman ameriken, oswa ki te deziyen pa gouvènman ameriken an kòm yon + peyi "teroris sipòte" ak (z) ou. yo pa nan lis nan okenn lis Gouvènman + ameriken nan pati entèdi oswa ki gen restriksyon. Ou pa pral itilize + Aplikasyon an pou okenn rezon entèdi pa lwa Etazini, ki gen ladan devlopman, + konsepsyon, fabrike oswa pwodiksyon de misil, zam nikleyè, chimik oswa + byolojik. Ou rekonèt ak dakò ke pwodwi, sèvis oswa teknoloji ki ofri pa psi yo + sijè a lwa yo kontwòl ekspòtasyon ak règleman nan peyi Etazini. Ou dwe konfòme + w ak lwa sa yo ak règleman epi yo pa dwe, san otorizasyon gouvènman Etazini, + ekspòtasyon, re-ekspòtasyon, oswa transfere pwodwi psi, sèvis oswa teknoloji, + swa dirèkteman oswa endirèkteman, nan nenpòt ki peyi an vyolasyon de lwa ak + règleman sa yo. +

          +

          + 6.13 Jwenn aksè ak telechaje aplikasyon an soti nan iTunes. + Sa ki annapre yo aplike a nenpòt ki App Store Sourcing Aplikasyon jwenn aksè a + oswa telechaje soti nan Apple App Store: +

          +
            +
          1. +
            +

            + Ou rekonèt epi mwen dakò ke (mwen) se Akò sa a konkli ant ou menm ak PCI + sèlman, epi yo pa Apple, ak (ii) PCI, pa Apple, se sèl responsab pou App + Store Sourcing Aplikasyon an ak kontni ladan l '. Sèvi ak ou nan App + Store Sourced Aplikasyon an dwe konfòme yo avèk Regleman yo App Store + nan Sèvis. +

            +
            +
          2. +
          3. +
            +

            + Ou rekonèt ke Apple pa gen okenn obligasyon tou sa pou founi okenn sèvis + antretyen ak respè pou aplikasyon magazen App. +

            +
            +
          4. +
          5. +
            +

            + Nan evènman an nan nenpòt ki echèk nan App Store Sourcing Aplikasyon an + konfòme yo ak nenpòt ki aplikab garanti, ou ka avèti Apple, ak Apple ap + ranbouse pri acha a pou App Store Sourcing Aplikasyon an pou ou ak nan + limit maksimòm nan lalwa aplikab, Apple. pa pral gen okenn obligasyon + lòt garanti tou sa ki gen rapò ak App Store Sourcing Aplikasyon an. Ant + PCI ak Apple, nenpòt ki lòt reklamasyon, pèt, dèt, domaj, depans oswa + depans atribuabl a nenpòt ki echèk nan konfòme yo ak nenpòt garanti yo + pral sèl responsablite nan psi. +

            +
            +
          6. +
          7. +
            +

            + Ou ak PCI rekonèt ke, ant PCI ak Apple, Apple pa responsab pou adrese + nenpòt reklamasyon ou gen oswa nenpòt reklamasyon nan nenpòt ki twazyèm + pati ki gen rapò ak App Store Sourcing Aplikasyon an oswa posesyon ou + epi sèvi ak nan App Store Sourcing Aplikasyon an, ki gen ladan, men se + pa sa sèlman: (i) reklamasyon responsablite pwodwi; (ii) nenpòt ki + reklamasyon ke App Store Sourcing Aplikasyon an echwe konfòme yo ak + nenpòt ki aplikab egzijans legal oswa regilasyon; ak (iii) reklamasyon + ki rive anba pwoteksyon konsomatè oswa lejislasyon ki sanble. +

            +
            +
          8. +
          9. +
            +

            + Ou ak PCI rekonèt ke, nan evènman an nan nenpòt ki reklamasyon + twazyèm-pati ki App Store Sourcing Aplikasyon an oswa posesyon ou ak + itilizasyon sa yo ki Aplikasyon App Store Sourced vyole dwa pwopriyete + entelektyèl twazyèm pati a, kòm ant psi ak Apple, psi, pa Apple. , pral + sèl responsab pou ankèt la, defans, règleman ak egzeyat nan nenpòt ki + reklamasyon enfraksyon pwopriyete sa yo nan limit ki nan Kontra sa-a + egzije. +

            +
            +
          10. +
          11. +
            +

            + Ou menm ak PCI rekonèt epi mwen dakò ke Apple, ak filiales Apple la, se + yo se benefisyè twazyèm pati nan Kontra sa-a kòm ki gen rapò ak lisans + ou nan App Store Sourcing Aplikasyon an, e ke, sou akseptasyon ou nan + tèm ak kondisyon nan Kontra sa-a, Apple ap gen dwa a (epi yo pral jije + yo te aksepte dwa a ... ) ranfòse Kontra sa a kòm ki gen rapò ak lisans + ou nan App Aplikasyon an Sourced magazen kont ou kòm yon benefisyè + twazyèm-pati ladan l '. +

            +
            +
          12. +
          13. +
            +

            + San yo pa limite nenpòt lòt tèm nan Kontra sa-a, ou dwe konfòme yo avèk + tout aplikab tèm twazyèm-pati nan akò lè w ap itilize App Store Sourcing + Aplikasyon an. +

            +
            +
          14. +
          +

          + 6.14 Plent Konsomatè. An akò avèk Kòd Sivil Kalifòni §1789.3, + ou ka rapòte plent bay Inite a Asistans Plent nan Divizyon Sèvis pou Konsomatè + nan Depatman Kalifòni nan Zafè Konsomatè nan Kalifòni lè w kontakte yo alekri + nan 1625 North Market Blvd., Suite N 112, Sacramento, CA 95834, oswa pa + telefòn nan (800) 952-5210. +

          +

          + 6.15 Tout Akò. Akò sa a se akò final, konplè ak eksklizif de + pati yo ki gen rapò ak matyè a nan sa a ak ranplase ak melanje tout diskisyon + anvan ant pati yo ki gen rapò ak matyè sa yo. +

          + + + + + +`; diff --git a/app/locales/ht.json b/app/locales/ht.json index 31928b4b98..2db9b5f06e 100644 --- a/app/locales/ht.json +++ b/app/locales/ht.json @@ -28,7 +28,7 @@ "default_news_site_name": "Nouvèl sou Chase Kowona (\"Safe Paths\")", "event_history_subtitle": "Konprann ekspozisyon pèsonèl ou daprè enfòmasyon Otorite a pataje.", "event_history_title": "Lis ekspozisyon", - "export_para_1": "Si ou teste pozitif pou Kowonaviris, tanpri fè pati pa ou a nan pataje lis lokalizasyon ou ak otorite lokal yo.", + "export_para_1": "Si ou teste pozitif pou Kowonaviris, tanpri fè pati pa ou a nan pataje lis lokalizasyon ou ak otorite lokal yo.", "export_para_2": "Lokalizasyon ou yo ap pataje sou fòm yon senp lis lè ak zòn, pap gen okenn enfòmasyon anplis ki prale.", "home_at_risk_header": "Ou ka gen risk", "home_at_risk_subsubtext": "Sa pa vle di ou enfekte.", @@ -112,5 +112,10 @@ "terms_of_use": "Règleman itilizasyon", "tested_positive_subtitle": "Done prive ou kapab transfere a Otorite a, konsève, oubyen pataje.", "tested_positive_title": "Pataje lis lokalizasyon" + }, + "onboarding": { + "eula_checkbox": "Mwen aksepte akò lisans la", + "eula_message": "*Ou dwe aksepte pou itilize Safe Paths 'Chase Kowona", + "eula_continue": "Kontinye" } } diff --git a/app/views/onboarding/Onboarding1.js b/app/views/onboarding/Onboarding1.js index 8c5ec0e1be..731f7fe15e 100644 --- a/app/views/onboarding/Onboarding1.js +++ b/app/views/onboarding/Onboarding1.js @@ -16,7 +16,7 @@ import languages, { setUserLocaleOverride, supportedDeviceLanguageOrEnglish, } from './../../locales/languages'; -import ButtonWrapper from '../../components/ButtonWrapper'; +import { EulaModal } from '../../components/EulaModal'; import NativePicker from '../../components/NativePicker'; import { Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; @@ -34,8 +34,10 @@ class Onboarding extends Component { } componentDidMount() { - getUserLocaleOverride(locale => { - this.setState({ locale }); + getUserLocaleOverride().then(locale => { + if (locale) { + this.setState({ locale }); + } }); } @@ -90,11 +92,11 @@ class Onboarding extends Component { - this.props.navigation.replace('Onboarding2')} - buttonColor={Colors.VIOLET} - bgColor={Colors.WHITE} + + this.props.navigation.replace('Onboarding2') + } + selectedLocale={this.state.locale} /> diff --git a/e2e/Onboarding.spec.js b/e2e/Onboarding.spec.js index 058cd7d6f8..ea285574fd 100644 --- a/e2e/Onboarding.spec.js +++ b/e2e/Onboarding.spec.js @@ -4,6 +4,7 @@ import Onboarding2 from './pages/Onboarding2.po.js'; import Onboarding3 from './pages/Onboarding3.po.js'; import Onboarding4 from './pages/Onboarding4.po.js'; import Onboarding5 from './pages/Onboarding5.po.js'; +import SignEula from './pages/SignEula.po.js'; describe('Onboarding visual appearance', () => { it('Navigates through the onboarding without visual regression', async () => { @@ -15,6 +16,10 @@ describe('Onboarding visual appearance', () => { await Onboarding1.takeScreenshot(); await Onboarding1.tapButton(); + await SignEula.sign(); + await SignEula.takeScreenshot(); + await SignEula.tapButton(); + await Onboarding2.isOnScreen(); await Onboarding2.takeScreenshot(); await Onboarding2.tapButton(); diff --git a/e2e/UnsignedEula.spec.js b/e2e/UnsignedEula.spec.js new file mode 100644 index 0000000000..3b56ac9bb6 --- /dev/null +++ b/e2e/UnsignedEula.spec.js @@ -0,0 +1,19 @@ +import Onboarding1 from './pages/Onboarding1.po.js'; +import SignEula from './pages/SignEula.po.js'; + +describe('Cannot continue without signing the EULA', () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + }); + }); + + it('Does not allow the user to proceed', async () => { + await Onboarding1.isOnScreen(); + await Onboarding1.tapButton(); + + await SignEula.tapButton(); + await SignEula.takeScreenshot(); + await device.takeScreenshot('Unsigned Eula Continue Attempt'); + }); +}); diff --git a/e2e/helpers/onboarding.js b/e2e/helpers/onboarding.js index b6dc494083..62fcbcb559 100644 --- a/e2e/helpers/onboarding.js +++ b/e2e/helpers/onboarding.js @@ -3,11 +3,15 @@ import Onboarding2 from '../pages/Onboarding2.po.js'; import Onboarding3 from '../pages/Onboarding3.po.js'; import Onboarding4 from '../pages/Onboarding4.po.js'; import Onboarding5 from '../pages/Onboarding5.po.js'; +import SignEula from '../pages/SignEula.po.js'; export const navigateThroughOnboarding = async () => { await Onboarding1.isOnScreen(); await Onboarding1.tapButton(); + await SignEula.sign(); + await SignEula.tapButton(); + await Onboarding2.isOnScreen(); await Onboarding2.tapButton(); diff --git a/e2e/pages/SignEula.po.js b/e2e/pages/SignEula.po.js new file mode 100644 index 0000000000..a6bfad4df1 --- /dev/null +++ b/e2e/pages/SignEula.po.js @@ -0,0 +1,20 @@ +/* eslint-disable */ +const buttonlabel = 'Continue'; +const signCheckboxLabel = 'I accept the licensing agreement'; +const screenshotText = 'EULA Accept Page'; + +class SignEula { + async tapButton() { + await element(by.label(buttonlabel)).tap(); + } + + async sign() { + await element(by.text(signCheckboxLabel)).tap(); + } + + async takeScreenshot() { + await device.takeScreenshot(screenshotText); + } +} + +export default new SignEula(); From ca4df17ee77f1c57d134c066935b796cf2db8f13 Mon Sep 17 00:00:00 2001 From: kpugsley Date: Fri, 24 Apr 2020 04:24:21 -0500 Subject: [PATCH 17/60] Add in a 6 hour time period between notifications (#665) * add in a 6 hour time period between notifications * oopsie... left a tracking variable. fixed. * comment fix * force the retrieved unix time to be a number, also more sensical log Co-authored-by: Ken Pugsley <> --- app/constants/history.js | 11 +++++++++++ app/helpers/Intersect.js | 12 +++++++++++- app/services/BackgroundTaskService.js | 3 +-- app/views/ChooseProvider.js | 5 +++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/constants/history.js b/app/constants/history.js index d488a3b57f..6860512a9f 100644 --- a/app/constants/history.js +++ b/app/constants/history.js @@ -14,6 +14,17 @@ export const DEFAULT_EXPOSURE_PERIOD_MINUTES = 5; */ export const CONCERN_TIME_WINDOW_MINUTES = 4 * 60; // 4 hours, in minutes +/** + * The value in minutes of how frequently we should check intersection data if + * there has been no change to the authorities + */ +export const MIN_CHECK_INTERSECT_INTERVAL = 6 * 60; + +/** + * The value in minutes for the background task service to register for firing + */ +export const INTERSECT_INTERVAL = 60 * 12; // 12 Hours, the value is received in minutes + /** * Format of a single history item * diff --git a/app/helpers/Intersect.js b/app/helpers/Intersect.js index 4252a9fc88..fe0829a73d 100644 --- a/app/helpers/Intersect.js +++ b/app/helpers/Intersect.js @@ -13,6 +13,7 @@ import { CONCERN_TIME_WINDOW_MINUTES, DEFAULT_EXPOSURE_PERIOD_MINUTES, MAX_EXPOSURE_WINDOW_DAYS, + MIN_CHECK_INTERSECT_INTERVAL, } from '../constants/history'; import { AUTHORITY_NEWS, @@ -259,7 +260,11 @@ export function checkIntersect() { ); asyncCheckIntersect().then(result => { - console.log('[intersect] completed: ', result); + if (result === null) { + console.log('[intersect] skipped'); + } else { + console.log('[intersect] completed: ', result); + } }); } @@ -270,6 +275,11 @@ export function checkIntersect() { * Returns the array of day bins (mostly for debugging purposes) */ async function asyncCheckIntersect() { + + // first things first ... is it time to actually try the intersection? + let last_checked_ms =Number(await GetStoreData(LAST_CHECKED)); + if (last_checked_ms + MIN_CHECK_INTERSECT_INTERVAL * 60 * 1000 > dayjs().valueOf()) return null; + // Set up the empty set of dayBins for intersections, and the array for the news urls let dayBins = getEmptyLocationBins(); let name_news = []; diff --git a/app/services/BackgroundTaskService.js b/app/services/BackgroundTaskService.js index 03ef82b4ca..3cddd7de55 100644 --- a/app/services/BackgroundTaskService.js +++ b/app/services/BackgroundTaskService.js @@ -1,9 +1,8 @@ import BackgroundFetch from 'react-native-background-fetch'; +import { INTERSECT_INTERVAL } from '../constants/history'; import { checkIntersect } from '../helpers/Intersect'; -const INTERSECT_INTERVAL = 60 * 12; // 12 Hours, the value is received in minutes - export function executeTask() { checkIntersect(); } diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index 6280cda516..29ca0e09f0 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -6,7 +6,6 @@ import { Dimensions, FlatList, Image, - SafeAreaView, StyleSheet, TouchableOpacity, View, @@ -31,7 +30,7 @@ import { AUTHORITIES_LIST_URL } from '../constants/authorities'; import colors from '../constants/colors'; import Colors from '../constants/colors'; import fontFamily from '../constants/fonts'; -import { AUTHORITY_SOURCE_SETTINGS } from '../constants/storage'; +import { AUTHORITY_SOURCE_SETTINGS, LAST_CHECKED } from '../constants/storage'; import { GetStoreData, SetStoreData } from '../helpers/General'; import { checkIntersect } from '../helpers/Intersect'; import languages from '../locales/languages'; @@ -81,6 +80,8 @@ class ChooseProviderScreen extends Component { componentWillUnmount() { BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); + // set the LAST_CHECKED time to 0, so the intersection will kick off + SetStoreData(LAST_CHECKED, 0); } fetchAuthoritiesList() { From 923bfe89576eefc4a928441baf5b06dd0ecbe95e Mon Sep 17 00:00:00 2001 From: Steve Penrod Date: Fri, 24 Apr 2020 04:35:15 -0500 Subject: [PATCH 18/60] Polish the EULA (#666) Several changes to make the EULA palatable: * Reduced the indents in the HTML with inline CSS * Reduced text size so it is small like legal text should be ;-) * Reducing some padding around elements to fit small Android 6 screen * Removed the scroll to botton -- injected javascript was not running, or at least not generating any calls to handleWebViewMessage() --- app/components/EulaModal.js | 8 ++++---- app/locales/eula/en_html.js | 11 +++++++++++ app/locales/eula/ht_html.js | 18 +++++++----------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/components/EulaModal.js b/app/components/EulaModal.js index 1c6ebaf703..cfa8da8a1c 100644 --- a/app/components/EulaModal.js +++ b/app/components/EulaModal.js @@ -39,7 +39,7 @@ const webViewScrollDetection = ` }; function scrollHandler() { - if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { + if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 30) { window.ReactNativeWebView.postMessage('end'); } else { window.ReactNativeWebView.postMessage('scroll'); @@ -76,7 +76,7 @@ export const EulaModal = ({ selectedLocale, continueFunction }) => { // Pull the EULA in the correct language, with en as fallback const html = EULA_FILES[selectedLocale] || en_html; - const canContinue = boxChecked && hasScrolledToEnd; + const canContinue = boxChecked; // && hasScrolledToEnd; return ( <> @@ -89,7 +89,7 @@ export const EulaModal = ({ selectedLocale, continueFunction }) => { - + setModalVisibility(false)}> @@ -143,7 +143,7 @@ const styles = StyleSheet.create({ backgroundColor: Colors.WHITE, }, ctaBox: { - padding: 25, + padding: 15, paddingTop: 0, backgroundColor: Colors.VIOLET_BUTTON, }, diff --git a/app/locales/eula/en_html.js b/app/locales/eula/en_html.js index c13fb29059..e629585444 100644 --- a/app/locales/eula/en_html.js +++ b/app/locales/eula/en_html.js @@ -5,6 +5,17 @@ export default ` + + diff --git a/app/locales/eula/ht_html.js b/app/locales/eula/ht_html.js index d20a4bf412..559dac1eb7 100644 --- a/app/locales/eula/ht_html.js +++ b/app/locales/eula/ht_html.js @@ -7,18 +7,14 @@ export default ` content="width=device-width, initial-scale=1.0"> From edd6efb0b945fa4fe642c475cceb4e9be1770d41 Mon Sep 17 00:00:00 2001 From: Steve Penrod Date: Fri, 24 Apr 2020 04:59:07 -0500 Subject: [PATCH 19/60] BUGFIX: Add authority wasn't immediate (#667) The 6-hour limit on recalculation of the intersection introduced and unintended problem with the Choose Health Authority page. It would not complete an Intersection process, so no new authority's URL would get pulled down until hours later. Now it happens immediately upon leaving the Choose page, like before. --- app/views/ChooseProvider.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index 29ca0e09f0..d61d6fc2b9 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -82,6 +82,9 @@ class ChooseProviderScreen extends Component { BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); // set the LAST_CHECKED time to 0, so the intersection will kick off SetStoreData(LAST_CHECKED, 0); + + // Force update, this will download any changed Healthcare Authorities + checkIntersect(); } fetchAuthoritiesList() { @@ -139,10 +142,7 @@ class ChooseProviderScreen extends Component { SetStoreData( AUTHORITY_SOURCE_SETTINGS, this.state.selectedAuthorities, - ).then(() => { - // Force updates immediately. - checkIntersect(); - }); + ); }, ); } else { @@ -172,10 +172,7 @@ class ChooseProviderScreen extends Component { SetStoreData( AUTHORITY_SOURCE_SETTINGS, this.state.selectedAuthorities, - ).then(() => { - // Force updates immediately. - checkIntersect(); - }); + ); }, ); } @@ -208,10 +205,7 @@ class ChooseProviderScreen extends Component { SetStoreData( AUTHORITY_SOURCE_SETTINGS, this.state.selectedAuthorities, - ).then(() => { - // Force updates immediately. - checkIntersect(); - }); + ); }, ); }, From 91b734602d1b803e3013a90b3709cebf62119c71 Mon Sep 17 00:00:00 2001 From: Steve Penrod Date: Fri, 24 Apr 2020 05:04:45 -0500 Subject: [PATCH 20/60] Hide unintented "What does this mean?" message (#668) The "What does this mean?" message was appearing on the Exposure History page even when you didn't have exposure. --- app/views/ExposureHistory/DetailedHistory.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/views/ExposureHistory/DetailedHistory.js b/app/views/ExposureHistory/DetailedHistory.js index 35042d864f..959ff9a75b 100644 --- a/app/views/ExposureHistory/DetailedHistory.js +++ b/app/views/ExposureHistory/DetailedHistory.js @@ -37,14 +37,17 @@ export const DetailedHistory = ({ history }) => { - ) : null} + ) : ( + <> + + {languages.t('history.what_does_this_mean')} + + + {languages.t('history.what_does_this_mean_para')} + + + )} - - {languages.t('history.what_does_this_mean')} - - - {languages.t('history.what_does_this_mean_para')} - {exposedDays.length ? ( From 56dbfd9261a785909379913d8c9ee11e9d1b0566 Mon Sep 17 00:00:00 2001 From: kpugsley Date: Fri, 24 Apr 2020 05:21:53 -0500 Subject: [PATCH 21/60] v1.0.0 (#669) v1.0.0 release --- android/app/build.gradle | 4 ++-- ios/COVIDSafePaths/Info.plist | 6 +++--- ios/COVIDSafePathsTests/Info.plist | 4 ++-- package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index db390f4e4a..b7d1f1143f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -137,8 +137,8 @@ android { testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - versionCode 23 - versionName "0.9.5" + versionCode 24 + versionName "1.0.0" } splits { abi { diff --git a/ios/COVIDSafePaths/Info.plist b/ios/COVIDSafePaths/Info.plist index d14667cc1a..36bbff8ad8 100644 --- a/ios/COVIDSafePaths/Info.plist +++ b/ios/COVIDSafePaths/Info.plist @@ -7,7 +7,7 @@ itms-apps LSApplicationCategoryType - + BGTaskSchedulerPermittedIdentifiers com.transistorsoft.fetch @@ -27,11 +27,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9.5 + 1.0.0 CFBundleSignature ???? CFBundleVersion - 23 + 24 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/ios/COVIDSafePathsTests/Info.plist b/ios/COVIDSafePathsTests/Info.plist index 0f90404b08..11d950655a 100644 --- a/ios/COVIDSafePathsTests/Info.plist +++ b/ios/COVIDSafePathsTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 0.9.5 + 1.0.0 CFBundleSignature ???? CFBundleVersion - 22 + 24 diff --git a/package.json b/package.json index 6d134dd198..63e3c37ff5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "covidsafepaths", - "version": "0.9.5", + "version": "1.0.0", "private": true, "scripts": { "start": "yarn && react-native start", From d694914b18f978a3123d6ee9d54050a129f6dddb Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Fri, 24 Apr 2020 15:52:08 -0500 Subject: [PATCH 22/60] Remove authority selection from onboarding (#679) Signed-off-by: Patrick Erichsen --- app/Entry.js | 6 - app/views/onboarding/Onboarding5.js | 330 +++++++++++++++++++------ app/views/onboarding/Onboarding6.js | 360 ---------------------------- 3 files changed, 259 insertions(+), 437 deletions(-) delete mode 100644 app/views/onboarding/Onboarding6.js diff --git a/app/Entry.js b/app/Entry.js index d2f9918bb2..47d7372fe1 100644 --- a/app/Entry.js +++ b/app/Entry.js @@ -19,7 +19,6 @@ import Onboarding2 from './views/onboarding/Onboarding2'; import Onboarding3 from './views/onboarding/Onboarding3'; import Onboarding4 from './views/onboarding/Onboarding4'; import Onboarding5 from './views/onboarding/Onboarding5'; -import Onboarding6 from './views/onboarding/Onboarding6'; import { SettingsScreen } from './views/Settings'; const Stack = createStackNavigator(); @@ -92,11 +91,6 @@ class Entry extends Component { component={Onboarding5} options={{ headerShown: false }} /> - { + let icon; + switch (status) { + case PermissionStatusEnum.UNKNOWN: + icon = IconUnknown; + break; + case PermissionStatusEnum.GRANTED: + icon = IconGranted; + break; + case PermissionStatusEnum.DENIED: + icon = IconDenied; + break; + } + return ( + + {title} + + + ); +}; + class Onboarding extends Component { constructor(props) { super(props); - this.state = { - selectedAuthorities: [], + notificationPermission: PermissionStatusEnum.UNKNOWN, + locationPermission: PermissionStatusEnum.UNKNOWN, }; + this.checkLocationStatus(); + this.checkNotificationStatus(); + } + + isLocationChecked() { + return this.state.locationPermission !== PermissionStatusEnum.UNKNOWN; + } + + isNotificationChecked() { + return this.state.notificationPermission !== PermissionStatusEnum.UNKNOWN; + } + + checkLocationStatus() { + // NEED TO TEST ON ANNDROID + let locationPermission; + if (isPlatformiOS()) { + locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; + } else { + locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + } + check(locationPermission) + .then(result => { + switch (result) { + case RESULTS.GRANTED: + this.setState({ + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + locationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }) + .catch(error => { + console.log('error checking location: ' + error); + }); + } + + checkNotificationStatus() { + checkNotifications().then(({ status }) => { + switch (status) { + case RESULTS.GRANTED: + this.setState({ + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }); } - componentDidMount() { - this.props.navigation.addListener('focus', this.getAuthorities); - this.getAuthorities(); + requestLocation() { + // NEED TO TEST ON ANNDROID + let locationPermission; + if (isPlatformiOS()) { + locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; + } else { + locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + } + request(locationPermission).then(result => { + switch (result) { + case RESULTS.GRANTED: + console.log('Location granted'); + this.setState({ + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + locationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + }); } - getAuthorities = () => { - GetStoreData(AUTHORITY_SOURCE_SETTINGS, false).then(result => { - if (result !== null) { - console.log('Retrieving settings from async storage:'); - console.log(result); - this.setState({ - selectedAuthorities: result, - }); - } else { - console.log('No stored authority settings.'); + requestNotification() { + requestNotifications(['alert', 'badge', 'sound']).then(({ status }) => { + switch (status) { + case RESULTS.GRANTED: + this.setState({ + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.UNAVAILABLE: + case RESULTS.BLOCKED: + this.setState({ + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; } }); - }; + } - onGoToSettingsPress = () => { - this.props.navigation.navigate('ChooseProviderScreen'); - }; + buttonPressed() { + if (!this.isLocationChecked()) { + this.requestLocation(); + } else if (!this.isNotificationChecked()) { + this.requestNotification(); + } else { + SetStoreData(PARTICIPATE, 'true'); // replaces "start" button + SetStoreData('ONBOARDING_DONE', true); + this.props.navigation.replace('LocationTrackingScreen'); + } + } - onButtonPress = () => { - this.props.navigation.replace('Onboarding6'); - }; + getTitleText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_location_header'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_notif_header'); + } else { + return languages.t('label.launch_done_header'); + } + } - renderAuthorities = () => { + getTitleTextView() { + if (!this.isLocationChecked() || !this.isNotificationChecked()) { + return ( + + {this.getTitleText()} + + ); + } else { + return ( + + {this.getTitleText()} + + ); + } + } + + getSubtitleText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_location_subheader'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_notif_subheader'); + } else { + return languages.t('label.launch_done_subheader'); + } + } + + getLocationPermission() { return ( - ( - - {item.key} - - )} - /> + <> + + + + ); - }; + } + + getNotificationsPermissionIfIOS() { + if (isPlatformiOS()) { + return ( + <> + + + + ); + } + return; + } + + getButtonText() { + if (!this.isLocationChecked()) { + return languages.t('label.launch_enable_location'); + } else if (!this.isNotificationChecked()) { + return languages.t('label.launch_enable_notif'); + } else { + return languages.t('label.launch_finish_set_up'); + } + } render() { return ( @@ -76,32 +265,23 @@ class Onboarding extends Component { backgroundColor='transparent' translucent /> + - - {languages.t('label.choose_provider_title')} - - - {languages.t('label.choose_provider_subtitle')} + {this.getTitleTextView()} + + {this.getSubtitleText()} - - {this.renderAuthorities()} + + {this.getLocationPermission()} + {this.getNotificationsPermissionIfIOS()} + + - - @@ -128,6 +308,12 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignSelf: 'center', }, + bigHeaderText: { + color: Colors.WHITE, + lineHeight: 48.5, + paddingTop: 52 - 48.5, // lineHeight hack + width: width * 0.7, + }, headerText: { color: Colors.WHITE, width: width * 0.8, @@ -139,8 +325,16 @@ const styles = StyleSheet.create({ width: width * 0.55, fontFamily: fontFamily.primaryRegular, }, + statusContainer: { + marginTop: '5%', + }, + divider: { + backgroundColor: Colors.DIVIDER, + height: 1, + marginVertical: '3%', + }, spacer: { - marginVertical: 7.5, + marginVertical: '5%', }, footerContainer: { position: 'absolute', @@ -148,24 +342,18 @@ const styles = StyleSheet.create({ marginBottom: '10%', alignSelf: 'center', }, - item: { - fontFamily: fontFamily.primaryRegular, - fontSize: 16, - padding: 10, - maxWidth: '90%', - color: Colors.WHITE, - }, - flatlistRowView: { + permissionContainer: { flexDirection: 'row', justifyContent: 'space-between', - paddingTop: 7, - paddingBottom: 5, - borderBottomWidth: 1, - borderColor: Colors.SILVER, - overflow: 'scroll', }, - listContainer: { - height: '25%', + permissionTitle: { + color: Colors.WHITE, + fontSize: 16, + alignSelf: 'center', + fontFamily: fontFamily.primaryRegular, + }, + permissionIcon: { + alignSelf: 'center', }, }); diff --git a/app/views/onboarding/Onboarding6.js b/app/views/onboarding/Onboarding6.js deleted file mode 100644 index b4ef241d68..0000000000 --- a/app/views/onboarding/Onboarding6.js +++ /dev/null @@ -1,360 +0,0 @@ -import React, { Component } from 'react'; -import { - Dimensions, - ImageBackground, - StatusBar, - StyleSheet, - View, -} from 'react-native'; -import { - PERMISSIONS, - RESULTS, - check, - checkNotifications, - request, - requestNotifications, -} from 'react-native-permissions'; -import { SvgXml } from 'react-native-svg'; - -import BackgroundImage from './../../assets/images/launchScreenBackground.png'; -import IconDenied from '../../assets/svgs/permissionDenied'; -import IconGranted from '../../assets/svgs/permissionGranted'; -import IconUnknown from '../../assets/svgs/permissionUnknown'; -import ButtonWrapper from '../../components/ButtonWrapper'; -import { Type, Typography } from '../../components/Typography'; -import Colors from '../../constants/colors'; -import fontFamily from '../../constants/fonts'; -import { PARTICIPATE } from '../../constants/storage'; -import { SetStoreData } from '../../helpers/General'; -import languages from '../../locales/languages'; -import { isPlatformiOS } from '../../Util'; - -const width = Dimensions.get('window').width; - -const PermissionStatusEnum = { - UNKNOWN: 0, - GRANTED: 1, - DENIED: 2, -}; - -const PermissionDescription = ({ title, status }) => { - let icon; - switch (status) { - case PermissionStatusEnum.UNKNOWN: - icon = IconUnknown; - break; - case PermissionStatusEnum.GRANTED: - icon = IconGranted; - break; - case PermissionStatusEnum.DENIED: - icon = IconDenied; - break; - } - return ( - - {title} - - - ); -}; - -class Onboarding extends Component { - constructor(props) { - super(props); - this.state = { - notificationPermission: PermissionStatusEnum.UNKNOWN, - locationPermission: PermissionStatusEnum.UNKNOWN, - }; - this.checkLocationStatus(); - this.checkNotificationStatus(); - } - - isLocationChecked() { - return this.state.locationPermission !== PermissionStatusEnum.UNKNOWN; - } - - isNotificationChecked() { - return this.state.notificationPermission !== PermissionStatusEnum.UNKNOWN; - } - - checkLocationStatus() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; - } - check(locationPermission) - .then(result => { - switch (result) { - case RESULTS.GRANTED: - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }) - .catch(error => { - console.log('error checking location: ' + error); - }); - } - - checkNotificationStatus() { - checkNotifications().then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); - } - - requestLocation() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; - } - request(locationPermission).then(result => { - switch (result) { - case RESULTS.GRANTED: - console.log('Location granted'); - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); - } - - requestNotification() { - requestNotifications(['alert', 'badge', 'sound']).then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); - } - - buttonPressed() { - if (!this.isLocationChecked()) { - this.requestLocation(); - } else if (!this.isNotificationChecked()) { - this.requestNotification(); - } else { - SetStoreData(PARTICIPATE, 'true'); // replaces "start" button - SetStoreData('ONBOARDING_DONE', true); - this.props.navigation.replace('LocationTrackingScreen'); - } - } - - getTitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_header'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_header'); - } else { - return languages.t('label.launch_done_header'); - } - } - - getTitleTextView() { - if (!this.isLocationChecked() || !this.isNotificationChecked()) { - return ( - - {this.getTitleText()} - - ); - } else { - return ( - - {this.getTitleText()} - - ); - } - } - - getSubtitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_subheader'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_subheader'); - } else { - return languages.t('label.launch_done_subheader'); - } - } - - getLocationPermission() { - return ( - <> - - - - - ); - } - - getNotificationsPermissionIfIOS() { - if (isPlatformiOS()) { - return ( - <> - - - - ); - } - return; - } - - getButtonText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_enable_location'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_enable_notif'); - } else { - return languages.t('label.launch_finish_set_up'); - } - } - - render() { - return ( - - - - - - {this.getTitleTextView()} - - {this.getSubtitleText()} - - - {this.getLocationPermission()} - {this.getNotificationsPermissionIfIOS()} - - - - - - - - - ); - } -} - -const styles = StyleSheet.create({ - backgroundImage: { - width: '100%', - height: '100%', - resizeMode: 'cover', - flex: 1, - }, - mainContainer: { - flex: 1, - }, - contentContainer: { - width: width * 0.9, - flex: 1, - justifyContent: 'center', - alignSelf: 'center', - }, - bigHeaderText: { - color: Colors.WHITE, - lineHeight: 48.5, - paddingTop: 52 - 48.5, // lineHeight hack - width: width * 0.7, - }, - headerText: { - color: Colors.WHITE, - width: width * 0.8, - }, - subheaderText: { - marginTop: '3%', - color: Colors.WHITE, - fontSize: 15, - width: width * 0.55, - fontFamily: fontFamily.primaryRegular, - }, - statusContainer: { - marginTop: '5%', - }, - divider: { - backgroundColor: Colors.DIVIDER, - height: 1, - marginVertical: '3%', - }, - spacer: { - marginVertical: '5%', - }, - footerContainer: { - position: 'absolute', - bottom: 0, - marginBottom: '10%', - alignSelf: 'center', - }, - permissionContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - permissionTitle: { - color: Colors.WHITE, - fontSize: 16, - alignSelf: 'center', - fontFamily: fontFamily.primaryRegular, - }, - permissionIcon: { - alignSelf: 'center', - }, -}); - -export default Onboarding; From c8457d62b2240470d94963401e5197c73d8f1e50 Mon Sep 17 00:00:00 2001 From: Matteo Crippa Date: Fri, 24 Apr 2020 22:56:07 +0200 Subject: [PATCH 23/60] Italian language updates (#626) --- app/locales/it.json | 59 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/app/locales/it.json b/app/locales/it.json index 8b60ba2bfe..1d80c723de 100644 --- a/app/locales/it.json +++ b/app/locales/it.json @@ -1,48 +1,51 @@ { + "Done": "Fatto", "history": { "no_exposure": "Nessuna esposizione", "possible_exposure": "Possibile esposizione", "possible_exposure_para": "E' possibile che tu sia stato in contatto o vicino ad una persona risultata positiva al COVID-19.", "what_does_this_mean": "Cosa significa?", - "what_does_this_mean_para": "In base alla tua cronologia GPS, e' possibile che tu sia stato in contatto o nelle vicinanze di una persona risultata positiva al COVID-19. Questo però non significa che tu sia infetto.\n\nPer maggiori informazioni o per sapere cosa fare, fai riferimento al sito della Mayo Clinic.", + "what_does_this_mean_para": "In base alla tua cronologia GPS, è possibile che tu sia stato in contatto o nelle vicinanze di una persona risultata positiva al COVID-19. Questo però non significa che tu sia infetto.\n\nPer maggiori informazioni o per sapere cosa fare, fai riferimento al sito della Mayo Clinic.", "what_if_no_symptoms": "E se non mostro nessun sintomo?", "what_if_no_symptoms_para": "Se non hai alcun sintomo, ma vuoi comunque effettuare un test, puoi presentarti al centro più vicino.\n\nLe persone che non mostrano alcun sintomo possono, in alcune circostanze, essere ancora contagiosi. Mantieni alta l'attenzione e pratica il distanziamento sociale, evita di venire in contatto con gruppi di persone o con persone ad alto rischio (anziani o persone affette da altre patologie), è importante non solo per te, ma anche per gli altri." }, "label": { "about_title": "Informazioni", - "authorities_add_button_label": "Aggiungi Autorità sanitaria", - "authorities_add_url": "Aggiungi autorità sanitaria via URL", - "authorities_desc": "Seleziona le autorità sanitarie nella tua area per ottenere i dati sulle possibilità di esposizione.\nSeleziona un nome dal registro globale o inserisci l'indirizzo internet fornito da un'autorità sanitaria che partecipa a Safe Paths", + "authorities_add_button_label": "Aggiungi istituzione sanitaria", + "authorities_add_url": "Aggiungi istituzione sanitaria via URL", + "authorities_desc": "Seleziona le istituzioni sanitarie nella tua area per ottenere i dati sulle possibilità di esposizione.\n\nSeleziona un nome dal registro globale o inserisci l'indirizzo internet fornito da una istituzione sanitaria che partecipa a Safe Paths.", "authorities_input_placeholder": "Incolla l'indirizzo qui", "authorities_no_sources": "Nessuna fonte di dati per ora", "authorities_removal_alert_cancel": "Annulla", "authorities_removal_alert_desc": "Sei sicuro di voler rimuovere questa fonte di dati sanitari?", "authorities_removal_alert_proceed": "Procedi", - "authorities_removal_alert_title": "Rimuovi autorità sanitaria", - "authorities_title": "Autorità sanitarie", - "choose_provider_subtitle": "Per essere informato sulla possibilità di esposizione devi aggiungere un'autorità sanitaria", - "choose_provider_title": "Seleziona le autorità sanitarie", + "authorities_removal_alert_title": "Rimuovi istituzione sanitaria", + "authorities_title": "Istituzioni sanitarie", + "choose_provider_subtitle": "Per essere informato sulle possibilità di esposizione devi aggiungere almeno una istituzione sanitaria", + "choose_provider_title": "Seleziona le istituzioni sanitarie", "commitment": "Missione", - "commitment_para": "Safe Paths registra i dati in piena sicurezza e controlla l'interazione con altre persone usando la tua posizione. I tuoi dati non lasceranno MAI il tuo telefono senza il tuo consenso", + "commitment_para": "Safe Paths registra i dati in piena sicurezza e controlla l'interazione con altre persone usando la tua posizione.\nI tuoi dati non lasceranno MAI il tuo telefono senza il tuo consenso", "default_news_site_name": "Novità su Safe Paths", - "event_history_subtitle": "Controlla la tua possibile esposizione sulla base delle informazioni condivise dalle autorità sanitarie", + "event_history_subtitle": "Controlla la tua possibile esposizione sulla base delle informazioni condivise dalle istituzioni sanitarie", "event_history_title": "Storico delle esposizioni", - "export_para_1": "Se risulti COVID-19 positivo, per favore aiutaci condividendo lo storico delle tue posizioni con le autorità sanitarie", - "export_para_2": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva", + "export_para_1": "Se risulti COVID-19 positivo, per favore aiutaci condividendo lo storico delle tue posizioni con le istituzioni sanitarie.", + "export_para_2": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva.", "home_at_risk_header": "Potresti esser stato esposto", "home_at_risk_subsubtext": "Questo non significa che tu sia infetto", "home_at_risk_subtext": "Sulla base del tuo storico GPS, è possibile che tu sia venuto a contatto o sia nelle vicinanze di qualcuno con diagnosi di COVID-19", "home_enable_location": "Attiva la Localizzazione", - "home_mayo_link_heading": "Più informazioni su COVID-19", - "home_mayo_link_label": "dalla Mayo Clinic", - "home_no_contact_header": "Nessuna esposizione riscontrata", - "home_no_contact_subtext": "Sulla base dei dati disponibili non sei stato nelle vicinanze di nessuno risultato positivo per COVID-19", + "home_mayo_link_heading": "Informati sul COVID-19", + "home_mayo_link_label": "grazie alla Mayo Clinic", + "home_no_contact_header": "Nessuna esposizione", + "home_no_contact_subtext": "Sulla base dei dati disponibili non dovresti essere entrato in contatto con persone positive al COVID-19", "home_setting_off_header": "Sconosciuto", "home_setting_off_subtext": "Non possiamo darti informazioni sull'esposizione al virus se non attivi la localizzazione.", "home_unknown_header": "Sconosciuto", "home_unknown_subtext": "Non è possibile sapere se sei stato esposto se non permetti alla app di accedere alla tua posizione", - "import_step_1": "1. Fai Login nel tuo account Google e scarica lo storico delle tue posizioni", - "import_step_2": "2. Dopo il download, apri di nuovo questa schermata. I dati saranno importati automaticamente", + "import_step_1": "Aggiungere lo storico delle localizzazioni da Google ti aiuta ad avere piu informazioni sulle tue posizioni passate.", + "import_step_2": "Prima di importare i dati, devi effettuare il 'Take out' dei dati dal sito di Google.", + "import_step_3": "Apri il sito di Google Takeout ed effettua l'export dei tuoi dati di posizione seguendo i seguenti step:\n1. Metodo di recapito: \"Aggiungi a Drive\"\n Frequenza: \"Esporta un archivio\"\n3. Tipo di file e dimensioni: \".zip\" e \"1GB\"\n4. Google ti invierà un messaggio via email al termine dell'esportazione.\n5. Ritorna in questa schermata per effettuare l'importazione dei dati.\n- Importa da Google Drive\n- Scarica con il browser e poi importa manualmente.\n Ti consigliamo l'uso del WiFi in quanto il file potrebbe essere di grandi dimensioni.", + "import_takeout": "Visita Google Takeout", "import_title": "Importa posizioni", "latest_news": "Ultime novità", "launch_done_header": "Fatto!", @@ -58,13 +61,13 @@ "launch_notif_header": "Le notifiche ti avviseranno se hai incrociato il tuo percorso con una persona che ha contratto l'infezione", "launch_notif_subheader": "Non ti daremo ulteriore fastidio se non per condividere aggiornamenti sulla tua potenziale esposizione", "launch_notification_access": "Abilita Notifiche", - "launch_screen1_header": "Il ritorno alla normalità comincia da qui", - "launch_screen2_header": "Ricevi notifiche se hai incrociato il percorso di chi è stato in seguito diagnosticato con COVID-19", - "launch_screen2_subheader": "Knowledge is power.", - "launch_screen3_header": "Se risulti positivo, puoi scegliere di donare i tuoi dati anonimamente", - "launch_screen3_subheader": "Il che aiuta l'intera comunità a proteggersi", - "launch_screen4_header": "Hai il pieno controllo. I dati sono salvati solo sul tuo dispositivo.", - "launch_screen4_subheader": "Se risulti positivo, solo tu puoi scegliere se condividerli o meno", + "launch_screen1_header": "Il ritorno alla normalità comincia da qui.", + "launch_screen2_header": "Ricevi notifiche se hai incrociato il percorso di chi è stato in seguito diagnosticato con COVID-19.", + "launch_screen2_subheader": "Sapere è potere.", + "launch_screen3_header": "Se risulti positivo, puoi scegliere di donare i tuoi dati anonimamente.", + "launch_screen3_subheader": "Il che aiuta l'intera comunità a proteggersi.", + "launch_screen4_header": "Hai il pieno controllo.\nI dati sono salvati solo sul tuo dispositivo.", + "launch_screen4_subheader": "Se risulti positivo, solo tu puoi scegliere se condividerli o meno.", "launch_set_up_phone": "Imposta il mio dispositivo", "legal_page_title": "Legale", "loading_public_data": "Caricando i dati…", @@ -78,18 +81,18 @@ "maps_import_disclaimer": "Safe Paths non ha alcuna affiliazione a Google e non condivide mai i tuoi dati", "maps_import_text": "Per capire se hai incontrato qualcuno con COVID-19 prima di scaricare questa app, puoi importare lo storico delle tue posizioni", "maps_import_title": "Google Maps", - "news_subtitle": "Leggi gli aggiornamenti su COVID-19 da parte delle tue autorità sanitare e nel mondo", + "news_subtitle": "Leggi gli aggiornamenti su COVID-19 da parte delle tue istituzioni sanitarie e nel mondo", "news_title": "Ultime novità", "no_data": "Nessun dato", "push_at_risk_message": "Hai incrociato il percorso di un paziente COVID-19", "push_at_risk_title": "Potresti essere a rischio di esposizione", "see_exposure_history": "Visualizza lo storico delle esposizioni", - "settings_title": "Dashboard", + "settings_title": "Settaggi", "share_location_data": "Condividi i tuoi dati GPS", "team": "Team", "team_para": "Il nostro team è composto da un consorzio di epidemiologi, ingegneri, data scientist, esperti di privacy digitale, professori e ricercatori dalle più importanti istituzioni, incluse: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young e Link Ventures", "terms_of_use": "Termini per l'utilizzo", - "tested_positive_subtitle": "I tuoi dati personali possono essere trasferiti alle autorità sanitarie, salvati o condivisi in altra maniera", + "tested_positive_subtitle": "I tuoi dati personali possono essere trasferiti alle istituzioni sanitarie, salvati o condivisi in altra maniera", "tested_positive_title": "Condividi lo storico delle posizioni" } } From d71c62a1358399bb8c4bdd4328dcb5075b62e07e Mon Sep 17 00:00:00 2001 From: alinelutz <57687521+alinelutz@users.noreply.github.com> Date: Fri, 24 Apr 2020 18:51:31 -0300 Subject: [PATCH 24/60] Translation to Brazilian Portuguese (#614) --- app/locales/languages.js | 2 + app/locales/pt_BR.json | 97 ++++++++++++++++++++++++++++++++++++++++ i18next-parser.config.js | 1 + 3 files changed, 100 insertions(+) create mode 100644 app/locales/pt_BR.json diff --git a/app/locales/languages.js b/app/locales/languages.js index dd39274ac1..d5efb123f9 100644 --- a/app/locales/languages.js +++ b/app/locales/languages.js @@ -18,6 +18,7 @@ import it from './it.json'; import ml from './ml.json'; import nl from './nl.json'; import pl from './pl.json'; +import pt_BR from './pt_BR.json'; import ro from './ro.json'; import ru from './ru.json'; import sk from './sk.json'; @@ -80,6 +81,7 @@ const DEV_LANGUAGES = __DEV__ ml: { label: 'മലയാളം', translation: ml }, nl: { label: 'Nederlands', translation: nl }, pl: { label: 'Polski', translation: pl }, + pt_BR: { label: 'Portugues do Brasil', translation: pt_BR }, ro: { label: 'Română', translation: ro }, ru: { label: 'Русский', translation: ru }, sk: { label: 'Slovak', translation: sk }, diff --git a/app/locales/pt_BR.json b/app/locales/pt_BR.json new file mode 100644 index 0000000000..1c57fe16d5 --- /dev/null +++ b/app/locales/pt_BR.json @@ -0,0 +1,97 @@ +{ + "history": { + "no_exposure": "Sem exposição conhecida", + "possible_exposure_para": "É possível que você tenha tido proximidade ou contato com uma pessoa que testou positivo para COVID-19", + "possible_exposure": "Exposição possível", + "what_does_this_mean_para": "Baseado no seu histórico de localização por GPS, é possível que você tenha estado próximo ou em contato com alguém diagnosticado com COVID-19. Isso não significa, necessariamente, que você tenha se infectado, apesar de ser possível.\n\nPara mais orientações, consulte o site da Mayo Clinic (site em inglês).", + "what_does_this_mean": "O que isso significa?", + "what_if_no_symptoms_para": "Caso queira fazer um teste, você pode verificar a disponibilidade em sua região para testes em pessoas assintomáticas.\n\nPessoas sem sintomas também podem transmitir a doença. Cuidados quanto ao distanciamento social, evitar aglomerações ou contato com grupos de risco (idosos e aqueles com problemas de saúde) são importantes para diminuir o seu risco e o dos outros.", + "what_if_no_symptoms": "E se eu não tiver sintomas?" + }, + "label": { + "about_title": "Sobre", + "authorities_add_button_label": "Adicionar Fonte Confiável", + "authorities_add_url": "Adicionar autoridade por URL", + "authorities_desc": "Escolha autoridades de saúde de sua região para obter dados de exposição. Selecione o nome a partir do registro mundial, ou entre o endereço da web fornecido pela autoridade que implementou Safe Paths.", + "authorities_input_placeholder": "Cole o endereço URL aqui", + "authorities_no_sources": "Sem dados até o momento", + "authorities_removal_alert_cancel": "Cancelar", + "authorities_removal_alert_desc": "Confirma a exclusão da autoridade de saúde como fonte de dados?", + "authorities_removal_alert_proceed": "Confirmar", + "authorities_removal_alert_title": "Excluir autoridade", + "authorities_title": "Fontes confiáveis", + "choose_provider_subtitle": "Para ser informado sobre exposições, você precisará selecionar uma autoridade de saúde.", + "choose_provider_title": "Escolha uma autoridade de saúde", + "commitment": "Compromisso", + "commitment_para": "Safe Paths registra e verifica de maneira segura a sua interação com outras pessoas, através da sua localização. Seus dados NÃO saírão do seu celular sem a sua autorização.", + "default_news_site_name": "Notícias Safe Paths", + "event_history_subtitle": "Entenda a sua exposição pessoal a partir de informações compartilhadas com as autoridades de saúde.", + "event_history_title": "Histórico de exposição", + "export_para_1": "Se você testou positivo para COVID-19, faça a sua parte, compartilhe o seu histórico de localização com as autoridades locais de saúde.", + "export_para_2": "O histórico de localização é uma simples lista de locais e horários, sem compartilhamento de outras informações.", + "home_at_risk_header": "Você pode estar exposto.", + "home_at_risk_subsubtext": "Isso não quer dizer que você está infectado.", + "home_at_risk_subtext": "Baseado no seu histórico de localização por GPS, é possível que você tenha estado próximo ou em contato com alguém com COVID-19.", + "home_enable_location": "Permitir uso de localização", + "home_mayo_link_heading": "Mais informações sobre COVID-19", + "home_mayo_link_label": "da Mayo Clinic", + "home_no_contact_header": "Sem contato conhecido", + "home_no_contact_subtext": "Baseado nos dados disponíveis, você não esteve próximo a pessoas que informaram ter COVID-19.", + "home_setting_off_header": "Desconhecido", + "home_setting_off_subtext": "Não é possível verificar se você está em risco se não permitir o uso do histórico de localização nas configurações.", + "home_unknown_header": "Desconhecido", + "home_unknown_subtext": "Não é possível verificar se você está em risco se não permitir que o aplicativo acesse a sua localização.", + "import_step_1": "Adicionar dados de localização do Google irá te ajudar a construir seu histórico recente de localização.", + "import_step_2": "Para importar, você precisa primeiro exportar os seus dados de localização do Google.", + "import_step_3": "Visite Google Takeout e exporte seu Histórico de Localização com a seguinte configuração: \n1. Método de envio: \"Adicionar ao Google Drive\" \n2. Frequência: \"Exportar uma vez\" \n3. Tipo e tamanho do arquivo: \".zip\" e \"1GB\" \n4. Google enviará um email quando a exportação for concluída \n5. Retorne aqui para importar localizações. Opções de importação: \n- Importe do Google Drive \n- Baixe do navegador para os arquivos locais do celular, e então importe. Deve estar conectado em WiFi já que os arquivos podem ser grandes.", + "import_takeout": "Visite Google Takeout", + "import_title": "Importar Localizações", + "latest_news": "Últimas Notícias", + "launch_done_header": "Tudo pronto", + "launch_done_subheader": "Você está pronto para começar. Lembre-se, você sempre poderá alterar suas preferências, quando quiser.", + "launch_enable_location": "Permitir Localização", + "launch_enable_notif": "Permitir Notificações", + "launch_finish_set_up": "Concluir definições", + "launch_get_started": "Começar", + "launch_location_access": "Acesso à localização", + "launch_location_header": "Para lembrar onde você esteve, o seu celular precisa salvar a sua localização.", + "launch_location_subheader": "Não se preocupe, a informação não sai do seu aparelho, a não ser que você decida compartilhar.", + "launch_next": "Próximo", + "launch_notif_header": "As notificações avisam se você cruzou com alguém infectado.", + "launch_notif_subheader": "O aplicativo somente envia notificações sobre o seu risco de exposição, nada mais.", + "launch_notification_access": "Permitir Notificações", + "launch_screen1_header": "O caminho de volta à normalidade começa aqui.", + "launch_screen2_header": "Seja notificado se você cruzou com alguém que veio a ser diagnosticado com COVID-19.", + "launch_screen2_subheader": "Conhecimento é poder.", + "launch_screen3_header": "Se você testar positivo, poderá optar por fornecer os seus dados de forma anônima", + "launch_screen3_subheader": "Isso ajuda a manter toda a sua comunidade protegida.", + "launch_screen4_header": "Você tem total controle. Os dados são salvos somente no seu celular.", + "launch_screen4_subheader": "Se você testar positivo, somente você pode tomar a decisão sobre o compartilhamento.", + "launch_set_up_phone": "Configurar meu celular", + "legal_page_title": "Legal", + "loading_public_data": "carregando dados...", + "location_disabled_message": "COVID Safe Paths requer serviços de localização.", + "location_disabled_title": "Localização Foi Desabilitada", + "location_enabled_message": "COVID Safe Paths está salvando de forma segura as suas coordenadas GPS nesse aparelho, a cada 5 minutos.", + "location_enabled_title": "COVID Safe Paths Habilitado", + "logging_active": "Localização Ativa", + "logging_inactive": "Localização Inativa", + "maps_import_button_text": "Importar localizações anteriores", + "maps_import_disclaimer": "Safe Paths não tem ligação com Google e nunca compartilha os seus dados.", + "maps_import_text": "Para saber se você encontrou com alguém com COVID-19 antes de instalar esse aplicativo, você pode importar seu histórico de localizações pessoais.", + "maps_import_title": "Google Maps", + "news_subtitle": "Leia as últimas atualizações sobre COVID de sua autoridade de saúde e em geral.", + "news_title": "Últimas notícias", + "no_data": "Sem dados", + "push_at_risk_message": "Você cruzou com um paciente COVID-19", + "push_at_risk_title": "Você pode estar em risco", + "see_exposure_history": "Veja seu histórico de exposição", + "settings_title": "Painel de controle", + "share_location_data": "Compartilhar dados de localização", + "team": "Equipe", + "team_para": "Nossa equipe é formada por epidemiologistas, engenheiros, cientistas de dados, especialistas em privacidade digital, professores e pesquisadores de instituições renomadas, como: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young, e Link Ventures.", + "terms_of_use": "Termos de uso", + "tested_positive_subtitle": "Seus dados privados podem ser transferidos para autoridades de saúde, salvos em backup, ou compartilhados.", + "tested_positive_title": "Compartilhar histórico de localização" + } +} diff --git a/i18next-parser.config.js b/i18next-parser.config.js index 5338084d83..822130c2ea 100644 --- a/i18next-parser.config.js +++ b/i18next-parser.config.js @@ -48,6 +48,7 @@ module.exports = { 'id', 'nl', 'pl', + 'pt_BR', 'ro', 'ru', 'sk', From 4749e2c622cfa9503fe342fd7086e20b91ced066 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Fri, 24 Apr 2020 19:07:21 -0700 Subject: [PATCH 25/60] Fix background to run to edge of screen on iOS (#682) --- app/components/NavigationBarWrapper.js | 9 ++++----- app/constants/colors.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/components/NavigationBarWrapper.js b/app/components/NavigationBarWrapper.js index a1c54d462b..08994e33ae 100644 --- a/app/components/NavigationBarWrapper.js +++ b/app/components/NavigationBarWrapper.js @@ -47,18 +47,17 @@ const NavigationBarWrapper = ({ children, title, onBackPress }) => { const themeNavBar = ({ theme }) => theme.navBar || Colors.VIOLET; -// TODO: this breaks transitions... -// const themeBackground = ({ theme }) => -// theme.background || Colors.INTRO_WHITE_BG; - const TopContainer = styled.SafeAreaView` flex: 0; background-color: ${themeNavBar}; `; +const themeBackground = ({ theme }) => + theme.background || Colors.INTRO_WHITE_BG; + const BottomContainer = styled.SafeAreaView` flex: 1; - background-color: ${Colors.INTRO_WHITE_BG}; + background-color: ${themeBackground}; `; const themeNavBarBorder = ({ theme }) => diff --git a/app/constants/colors.js b/app/constants/colors.js index b26014fd8f..408d6f1a57 100644 --- a/app/constants/colors.js +++ b/app/constants/colors.js @@ -8,7 +8,7 @@ const colors = { PRIMARY_TEXT: '#000', SUCCESS: '#41dca4', // Green WARNING: '#ffc000', // Orange - VIOLET_ALPHA_06: 'rgba(105, 121, 248, 0.06)', + VIOLET_ALPHA_06: '#f7f8ff', // cannot use transparency VIOLET: '#4051DB', TRANSPARENT: 'transparent', From 2b95fd40ec5c421c361b8eb1419ace8743926619 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Fri, 24 Apr 2020 19:09:08 -0700 Subject: [PATCH 26/60] [i18n] Don't translate terms_of_use_url (#642) --- app/Entry.js | 4 +- app/locales/ar.json | 15 +- app/locales/en.json | 1 - app/views/Licenses.js | 153 +++++++----------- app/views/__tests__/Licenses.spec.js | 8 +- .../__snapshots__/Licenses.spec.js.snap | 33 ++-- 6 files changed, 81 insertions(+), 133 deletions(-) diff --git a/app/Entry.js b/app/Entry.js index 47d7372fe1..9fbbb7d690 100644 --- a/app/Entry.js +++ b/app/Entry.js @@ -11,7 +11,7 @@ import ChooseProviderScreen from './views/ChooseProvider'; import { ExportScreen } from './views/Export'; import { ExposureHistoryScreen } from './views/ExposureHistory/ExposureHistory'; import ImportScreen from './views/Import'; -import LicencesScreen from './views/Licenses'; +import { LicensesScreen } from './views/Licenses'; import LocationTracking from './views/LocationTracking'; import NewsScreen from './views/News'; import Onboarding1 from './views/onboarding/Onboarding1'; @@ -123,7 +123,7 @@ class Entry extends Component { /> { + const { t } = useTranslation(); - handleBackPress = () => { - this.backToMain(); - return true; + const backToMain = () => { + navigation.goBack(); }; - handleTermsOfUsePressed() { - Linking.openURL(languages.t('label.terms_of_use_url')); - } + const handleBackPress = () => { + backToMain(); + return true; + }; - componentDidMount() { - BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); - } + const handleTermsOfUsePressed = () => { + Linking.openURL(TERMS_OF_USE_URL); + }; - componentWillUnmount() { - BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); - } + useEffect(() => { + BackHandler.addEventListener('hardwareBackPress', handleBackPress); + return () => { + BackHandler.removeEventListener('hardwareBackPress', handleBackPress); + }; + }); - getLicenses() { - var result = ''; + function getLicenses() { + let result = ''; result += ''; result += ''; - for (var i = 0; i < licenses.terms_and_licenses.length; i++) { - var element = licenses.terms_and_licenses[i]; + for (let i = 0; i < licenses.terms_and_licenses.length; i++) { + const element = licenses.terms_and_licenses[i]; result += '

          ' + element.name + '

          '; result += element.text.replace(/\n/g, '
          '); @@ -62,48 +62,43 @@ class LicensesScreen extends Component { return result; } - render() { - return ( - - - - - - + return ( + + + + + + + - - - Linking.openURL(languages.t('label.terms_of_use_url')) - }> - {languages.t('label.terms_of_use')} - - + Linking.openURL(TERMS_OF_USE_URL)}> + {t('label.terms_of_use')} + - + - - ); - } -} + + + ); +}; const styles = StyleSheet.create({ - // Container covers the entire screen contentContainer: { flexDirection: 'column', width: '100%', @@ -111,51 +106,15 @@ const styles = StyleSheet.create({ paddingHorizontal: 26, flex: 1, }, - row: { - flexDirection: 'row', - color: Colors.PRIMARY_TEXT, - alignItems: 'flex-start', - }, - valueName: { - color: Colors.VIOLET_TEXT, - fontSize: 20, - fontFamily: fontFamily.primaryMedium, - marginTop: 9, - }, - value: { - color: Colors.VIOLET_TEXT, - fontSize: 20, - fontFamily: fontFamily.primaryMedium, - marginTop: 9, - }, - valueSmall: { - color: Colors.VIOLET_TEXT, - fontSize: 16, - fontFamily: fontFamily.primaryMedium, - marginTop: 9, - }, termsInfoRow: { flexDirection: 'row', justifyContent: 'space-between', backgroundColor: Colors.SILVER, - }, - termsInfoContainer: { - flexDirection: 'column', - justifyContent: 'space-between', - alignContent: 'flex-end', padding: 15, }, - mainTermsHeader: { - textAlign: 'left', - color: Colors.MISCHKA, - fontSize: 20, - fontFamily: fontFamily.primaryBold, - }, arrowContainer: { alignSelf: 'center', paddingRight: 20, paddingLeft: 20, }, }); - -export default LicensesScreen; diff --git a/app/views/__tests__/Licenses.spec.js b/app/views/__tests__/Licenses.spec.js index 99d8f3b3af..62102bf9eb 100644 --- a/app/views/__tests__/Licenses.spec.js +++ b/app/views/__tests__/Licenses.spec.js @@ -1,10 +1,12 @@ import 'react-native'; + +import { render } from '@testing-library/react-native'; import React from 'react'; -import {render} from '@testing-library/react-native'; -import Licenses from '../Licenses'; + +import { LicensesScreen } from '../Licenses'; it('renders correctly', () => { - const {asJSON} = render(); + const { asJSON } = render(); expect(asJSON()).toMatchSnapshot(); }); diff --git a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap index e90131ea07..5a0f41a7d6 100644 --- a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap @@ -164,36 +164,25 @@ exports[`renders correctly 1`] = ` "flexDirection": "row", "justifyContent": "space-between", "opacity": 1, + "padding": 15, } } > - - - Terms of use - - + Terms of use + Date: Sat, 25 Apr 2020 13:14:04 -0500 Subject: [PATCH 27/60] Fix formatting EULAs (#671) --- app/locales/eula/en_html.js | 1181 +++++++++++++++-------------------- app/locales/eula/ht_html.js | 76 +-- 2 files changed, 543 insertions(+), 714 deletions(-) diff --git a/app/locales/eula/en_html.js b/app/locales/eula/en_html.js index e629585444..0bda31c289 100644 --- a/app/locales/eula/en_html.js +++ b/app/locales/eula/en_html.js @@ -19,682 +19,511 @@ export default ` -

          - Terms of Use -

          -

          - Last Updated Date: April 23, 2020 -

          -

          - PLEASE READ THIS TERMS OF USE AGREEMENT (THE “TERMS OF USE” OR - “AGREEMENT”) CAREFULLY. THE SAFE PATH MOBILE APPLICATION (THE - “APPLICATION”), AND THE FEATURES AND INFORMATION IN IT ARE CONTROLLED BY PATH - CHECK, INC. (“PCI”). THESE TERMS OF USE GOVERN THE USE OF THE APPLICATION AND - APPLY TO ALL USERS USING THE APPLICATION IN ANY WAY, INCLUDING THE FEATURES THEREIN. BY CLICKING - ON THE “I ACCEPT” BUTTON AND/OR DOWNLOADING THE APPLICATION, YOU REPRESENT THAT (1) YOU HAVE - READ, UNDERSTAND, AND AGREE TO BE BOUND BY THE TERMS OF USE, (2) YOU ARE OF LEGAL AGE TO FORM A - BINDING CONTRACT WITH PCI, AND (3) YOU HAVE THE AUTHORITY TO ENTER INTO THE TERMS OF USE. - IF YOU DO NOT AGREE TO BE BOUND BY THE TERMS OF USE, YOU MAY NOT ACCESS OR USE THE - APPLICATION. -

          -

          - PLEASE BE AWARE THAT SECTION 13 OF THIS AGREEMENT, BELOW, CONTAINS PROVISIONS GOVERNING - HOW DISPUTES THAT YOU AND WE HAVE AGAINST EACH OTHER ARE RESOLVED, INCLUDING, WITHOUT - LIMITATION, ANY DISPUTES THAT AROSE OR WERE ASSERTED PRIOR TO THE EFFECTIVE DATE OF THIS - AGREEMENT. IN PARTICULAR, IT CONTAINS AN ARBITRATION AGREEMENT WHICH WILL, WITH LIMITED - EXCEPTIONS, REQUIRE DISPUTES BETWEEN US TO BE SUBMITTED TO BINDING AND FINAL ARBITRATION. - UNLESS YOU OPT OUT OF THE ARBITRATION AGREEMENT: (1) YOU WILL ONLY BE PERMITTED TO PURSUE - DISPUTES OR CLAIMS AND SEEK RELIEF AGAINST US ON AN INDIVIDUAL BASIS, NOT AS A PLAINTIFF OR - CLASS MEMBER IN ANY CLASS OR REPRESENTATIVE ACTION OR PROCEEDING; AND (2) YOU ARE WAIVING YOUR - RIGHT TO PURSUE DISPUTES OR CLAIMS AND SEEK RELIEF IN A COURT OF LAW AND TO HAVE A JURY TRIAL. - -

          -

          - ANY DISPUTE, CLAIM OR REQUEST FOR RELIEF RELATING IN ANY WAY TO YOUR USE OF THE - APPLICATION WILL BE GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF - MASSACHUSETTS, CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT TO ANY - PRINCIPLES THAT PROVIDE FOR THE APPLICATION OF THE LAW OF ANY OTHER JURISDICTION. THE UNITED - NATIONS CONVENTION ON CONTRACTS FOR THE INTERNATIONAL SALE OF GOODS IS EXPRESSLY EXCLUDED FROM - THIS AGREEMENT. -

          -

          - PLEASE NOTE THAT THIS AGREEMENT IS SUBJECT TO CHANGE BY PCI IN ITS SOLE DISCRETION AT ANY TIME. - When changes are made, PCI will make a new copy of the Terms of Use available within the - Application. We will also update the “Last Updated” date at the top of the Terms of Use. Your - continued use of the Application after a change to the Terms of Use is made constitutes - acceptance. PLEASE REGULARLY CHECK THE APPLICATION TO VIEW THE MOST CURRENT TERMS. -

          -
            - -
          1. - USE OF THE APPLICATION. -
              - -
            1. - Definitions. As used in these Terms of Use, the following definitions - apply: -

              - “App User” means an individual natural person who has downloaded the - Application under these Terms of Use. -

              -

              - “Location History” means location data collected from an App User’s - mobile device leveraging GPS, Bluetooth and/or other features or software, that the App - User elects to have recorded and stored within the Application installed on the App - User’s mobile device. -

              -

              - “Publicly Available Safe Places Data” means anonymized maps of public - places where individuals diagnosed with Covid-19 have visited and data files of times - they visited such places, as compiled and created by a third party, and made available - via such third party’s Third Party Safe Places Web App. -

              -

              - “Safe Places Software” means open-source software, available at https://github.com/tripleblindmarket/safe-places, - that facilitates contact tracing and related tasks, and is designed for use by third - parties, such as government agencies. -

              -

              - “Third Party Safe Places Web App” means a web-based application owned - and operated by a third party, such as a government agency, that employs Safe Places - Software to, among other things, publish Publicly Available Safe Places Data. -

              -
                - -
              1. - Application Features and Functionality. The Application is designed - to enable App Users, at their option: - (a) to record their Location History and to store such data locally in the Application - downloaded to their mobile device, (b) to access Publicly Available Safe Places Data - from one or more Third Party Safe Places Web Apps and download it to the Safe Paths - App downloaded on their mobile device, and (c) to share their stored Location History - with third parties that they choose. - -
              2. Application License. The Application and the information and - content available therein are protected by copyright laws throughout the world. - Subject to your compliance with this Agreement, PCI grants you a limited - non-exclusive, non-transferable, non-sublicensable, revocable license to download, - install and use a copy of the Application on a single mobile device or computer that - you own or control and to run such copy of the Application solely for your own - personal or internal business purposes. Certain components or libraries included in or - bundled with the Application constitute open source software and are licensed under - open source licenses. To the extent required by such open source licenses, the terms - of such licenses will apply in lieu of the terms of this Section 1.3, solely with - respect to those components or libraries that are licensed under such open source - licenses. For a copy of such open source licenses, visit https://github.com/tripleblindmarket/covid-safe-paths/blob/develop/LICENSE. - Furthermore, with respect to any Application accessed through or downloaded from the - Apple App Store (an “App Store Sourced Application”), you will only - use the App Store Sourced Application (a) on an Apple-branded product that runs the - iOS (Apple’s proprietary operating system) and (b) as permitted by the “Usage Rules” - set forth in the Apple App Store Terms of Service. Notwithstanding the first sentence - in this section, with respect to any Application accessed through or downloaded from - the Google Play store (a “Google Play Sourced Application”), you may - have additional license rights with respect to use of the Application on a shared - basis within your designated family group. - -
              3. Updates. You understand that to keep the Application most useful - for App Users and to accommodate bug fixes and other technological changes, PCI may - make updates to the Application after you download the Application. These updates will - be made available to App Users from the third party from whom you received the - Application license, e.g., the Apple App Store or Google Play (each, an “App - Store”). You will have the ability, at your option, to download any updates - to the Application that we make freely available through such channels. We do not - require you to install any updates in order for you to continue using the Application - after we make such updates available. This is because we do not retain any information - about you when you download the Application. Therefore, we will not know whether you - have installed any such updates. Accordingly, you acknowledge and agree that if you - elect NOT to install available updates, the Application may not operate in accordance - with publicly available documentation regarding the features and functionality of the - Application, which documentation may be updated after the time you originally - downloaded it. In addition, you may need to update third-party software from time to - time in order to use the Application. Any updates issued by PCI and installed by you - shall be deemed the Application and shall be governed by this Safe Places EULA or any - subsequent end user license agreement accompanying the update. - -
              4. Necessary Equipment and Software. You must provide all equipment - and software necessary to download and use the Application and to connect to any third - party services or sites, including but not limited to, a mobile device that is - suitable to connect with and use the Application. You are solely responsible for any - fees, including Internet connection or mobile fees, that you incur when accessing the - Application. - -
              5. Responsibility for Location History and Other Data Collected and Stored by - You. PCI has no access to the Location History or any other data you - collect, receive and store in the installed Application or otherwise on your mobile - device. Therefore, PCI has no responsibility or liability for the deletion or accuracy - of the Location History or other data you collect, receive or store in the Application - or the failure to store, transmit or receive transmission of Location History or other - data; or the security, privacy, storage, or transmission of other communications - originating with or involving use of the Application. -
              6. -
              - -
            2. OWNERSHIP. -
                - -
              1. - Application. Except with respect to your Location History and other - data you may collect, retrieve from third parties and store in the Application on your - device, you agree that PCI and its suppliers own all rights, title and interest in and - to the Application. You will not remove, alter or obscure any copyright, trademark, - service mark or other proprietary rights notices incorporated in or accompanying the - Application. - -
              2. Trademarks. COVID Safe Paths and all related graphics, logos, - service marks and trade names used on or in connection with the Application or in - connection therewith are the trademarks of PCI and may not be used without permission - in connection with your, or any third-party, products or services. Other trademarks, - service marks and trade names that may appear on or in the Application are the - property of their respective owners. -
              3. -
              - -
            3. COLLECTION AND USE OF YOUR PERSONAL INFORMATION. You acknowledge that - the Application may collect personal information about you (through your choice to store - information in the Application or through automatic technology tools), including your - Location History. You acknowledge that the Application may provide you with opportunities - to share personal information about yourself, including your Location History with others. - All personal information in connection with this Application is subject to our Privacy - Policy. By downloading, installing, using, and providing personal information to or - through this Application, you consent to all actions taken by us with respect to your - personal information in compliance with the Privacy Policy. For some features of the - Application, specific consent is required. In addition, you may choose to disclose, - through other means not associated with the Application, any part of your personal - information to family members, doctors, health care providers, governmental agencies, or - other individuals or entities. We recommend that you make your choices regarding sharing - your personal information, through the Application or otherwise, carefully. We will have - no liability for any consequences that may result because you have released or shared - information, through the Application or otherwise, with a third party. - -
            4. FEEDBACK. If you provide PCI with any ideas, suggestions, documents, - and/or proposals (“Feedback”) via email or another means, you do so at - your own risk and you acknowledge and agree that PCI has no obligations (including without - limitation obligations of confidentiality) with respect to such Feedback. You represent - and warrant that you have all rights necessary to submit the Feedback. You hereby grant to - PCI a fully paid, royalty-free, perpetual, irrevocable, worldwide, non-exclusive, and - fully sublicensable right and license to use, reproduce, perform, display, distribute, - adapt, modify, re-format, create derivative works of, and otherwise commercially or - non-commercially exploit in any manner, any and all Feedback, and to sublicense the - foregoing rights, in connection with the operation and maintenance of the Application, any - other PCI products or services, or PCI’s business. - -
            5. APP STORES. You acknowledge and agree that the availability of the - Application is dependent on the App Store from whom you received the Application license. - You acknowledge that this Agreement is between you and PCI and not with the App Store. - PCI, not the App Store, is solely responsible for the Application, including the content - PCI makes available therein, and the maintenance, support services, and warranty therefor, - and addressing any claims relating thereto (e.g., product liability, legal compliance or - intellectual property infringement). In order to use the Application, you must have access - to a wireless network, and you agree to pay all fees associated with such access. You also - agree to pay all fees (if any) charged by the App Store in connection with the - Application. You agree to comply with, and your license to use the Application is - conditioned upon your compliance with all terms of agreement imposed by the applicable App - Store when using the Application. You acknowledge that the App Store (and its - subsidiaries) are third-party beneficiaries of this Agreement and will have the right to - enforce it. - -
            6. FEES. No fees shall be payable under this Agreement for the rights - granted under this Agreement. You acknowledge and agree that this fee arrangement is made - in consideration for the mutual covenants set forth in this Agreement, including your - obligations hereunder, and the disclaimers, exclusions, and limitations of liability set - forth herein. - -
            7. INDEMNIFICATION. You agree to indemnify and hold PCI, its parents, - subsidiaries, affiliates, officers, employees, volunteers, agents, partners, suppliers, - and licensors (each, a “PCI Party” and collectively, the “PCI - Parties”) harmless from any losses, costs, liabilities and expenses (including - reasonable attorneys’ fees) relating to or arising out of any and all of the following: - (a) your use of, or inability to use the Application; (b) your violation of this - Agreement; (c) your violation of any rights of another party; or (d) your violation of any - applicable laws, rules or regulations. PCI reserves the right, at its own cost, to assume - the exclusive defense and control of any matter otherwise subject to indemnification by - you, in which event you will fully cooperate with PCI in asserting any available defenses. - This provision does not require you to indemnify any of the PCI Parties for any - unconscionable commercial practice by such party or for such party’s fraud, deception, - false promise, misrepresentation or concealment, suppression or omission of any material - fact in connection with the Application provided hereunder. You agree that the provisions - in this section will survive any termination of this Agreement and/or your use of or - access to Application. - -
            8. DISCLAIMER OF WARRANTIES AND CONDITIONS. -
                - -
              1. - As Is. YOU EXPRESSLY UNDERSTAND AND AGREE THAT TO THE EXTENT - PERMITTED BY APPLICABLE LAW, YOUR USE OF THE APPLICATION IS AT YOUR SOLE RISK, AND THE - APPLICATION IS PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS, WITH ALL FAULTS. THE - PCI PARTIES EXPRESSLY DISCLAIM ALL WARRANTIES, REPRESENTATIONS, AND CONDITIONS OF ANY - KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NON-INFRINGEMENT ARISING FROM USE OF THE APPLICATION. -
                  - -
                1. - THE PCI PARTIES MAKE NO WARRANTY, REPRESENTATION OR CONDITION THAT: (1) THE - APPLICATION OR ITS FEATURES WILL MEET YOUR REQUIREMENTS; (2) YOUR USE OF THE - APPLICATION WILL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; OR (3) THE - RESULTS THAT MAY BE OBTAINED FROM USE OF THE APPLICATION WILL BE ACCURATE OR - RELIABLE. - -
                2. ANY CONTENT DOWNLOADED FROM OR OTHERWISE ACCESSED THROUGH THE APPLICATION IS - ACCESSED AT YOUR OWN RISK, AND YOU SHALL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO - YOUR PROPERTY, INCLUDING, BUT NOT LIMITED TO, YOUR COMPUTER SYSTEM AND ANY DEVICE - YOU USE TO ACCESS THE APPLICATION, OR ANY OTHER LOSS THAT RESULTS FROM ACCESSING - SUCH CONTENT. - -
                3. THE AVAILABILITY OF THE APPLICATION AND ITS FEATURES MAY BE SUBJECT TO DELAYS, - CANCELLATIONS AND OTHER DISRUPTIONS. PCI MAKES NO WARRANTY, REPRESENTATION OR - CONDITION WITH RESPECT TO THE APPLICATION OR ITS FEATURES, INCLUDING BUT NOT - LIMITED TO, THE QUALITY, EFFECTIVENESS, REPUTATION AND OTHER CHARACTERISTICS - THEREOF. - -
                4. NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED FROM PCI OR THROUGH - THE APPLICATION WILL CREATE ANY WARRANTY NOT EXPRESSLY MADE HEREIN. - -
                5. FROM TIME TO TIME, PCI MAY OFFER NEW “BETA” FEATURES OR TOOLS WITH WHICH ITS - USERS MAY EXPERIMENT. SUCH FEATURES OR TOOLS ARE OFFERED SOLELY FOR EXPERIMENTAL - PURPOSES AND WITHOUT ANY WARRANTY OF ANY KIND, AND MAY BE MODIFIED OR DISCONTINUED - AT PCI’S SOLE DISCRETION. THE PROVISIONS OF THIS SECTION APPLY WITH FULL FORCE TO - SUCH FEATURES OR TOOLS. -
                6. -
                - -
              2. NOT INTENDED AS MEDICAL ADVICE. YOU ACKNOWLEDGE THAT THE - INFORMATION IN THE APPLICATION IS PROVIDED FOR GENERAL INFORMATIONAL PURPOSES ONLY. IT - IS NOT INTENDED AS MEDICAL ADVICE OF ANY KIND NOR IS IT INTENDED TO DIAGNOSE, TREAT, - CURE OR PREVENT ANY DISEASE OR MEDICAL CONDITION. THE INFORMATION PRESENTED IN THE - APPLICATION SHOULD NOT BE INTERPRETED OR CONSTRUED IN ANY WAY AS A REPLACEMENT OR - SUBSTITUTE FOR MEDICAL ADVICE PROVIDED BY YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE - PROVIDER. YOU SHOULD NOT DISREGARD, AVOID OR DELAY OBTAINING MEDICAL ADVICE OR - TREATMENT FROM YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER DUE TO ANY - INFORMATION PROVIDED IN THE APPLICATION. UNDER NO CIRCUMSTANCES SHOULD YOU ALTER YOUR - EXISTING MEDICAL TREATMENT, MEDICATION REGIMEN, OR ANY OTHER RELATED HEALTHCARE - ACTIVITIES BASED ON ANY INFORMATION PROVIDED IN THE APPLICATION. IT IS IMPORTANT FOR - YOU TO DISCUSS YOUR TREATMENT OPTIONS, AND ANY QUESTIONS THAT YOU MAY HAVE, WITH YOUR - DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER. - -
              3. In making the Application available for download, PCI’s goal is to provide - individuals with a useful tool for contact tracing. However, the utility of the - Application’s features is dependent upon a number of factors that are outside the - control of PCI, such as the reliability of GPS sensors, whether individuals are - carrying their mobile devices, as well as the accuracy, reliability, availability, - effectiveness or correct use of individual’s mobile devices and GPS sensors, all of - which are used to create Location History, and for any Publicly Available Safe Places - Data that you may upload into your Application from third party source, the accuracy - and completeness of such data. Your Location History or other data may be unavailable, - inaccurate or incomplete. Use of the Application should not replace your good judgment - and common sense. - -
              4. No Liability for Conduct of Third Parties. YOU ACKNOWLEDGE AND - AGREE THAT PCI PARTIES ARE NOT LIABLE, AND YOU AGREE NOT TO SEEK TO HOLD PCI PARTIES - LIABLE, FOR THE CONDUCT OR OMISSIONS OF THIRD PARTIES, INCLUDING THE ACTIONS OF ANY - THIRD PARTY OPERATING A THIRD PARTY SAFE PLACES WEB APP OR ANY GOVERNMENTAL AGENCY - WITH WHICH YOU CHOOSE TO INTERACT IN CONNECTION WITH YOUR USE OF THE APPLICATION, AND - THAT THE RISK OF INJURY FROM SUCH THIRD PARTIES RESTS ENTIRELY WITH YOU. -
              5. -
              - -
            9. LIMITATION OF LIABILITY. -
                - -
              1. - Disclaimer of Certain Damages. YOU UNDERSTAND AND AGREE THAT IN NO - EVENT SHALL THE PCI PARTIES BE LIABLE FOR ANY LOSS OF PROFITS, PERSONAL INJURY, - PROPERTY DAMAGE, WRONGFUL DEATH, REVENUE OR DATA, INDIRECT, INCIDENTAL, SPECIAL, OR - CONSEQUENTIAL DAMAGES, OR DAMAGES OR COSTS DUE TO LOSS OF PRODUCTION OR USE, BUSINESS - INTERRUPTION, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, IN EACH CASE WHETHER OR NOT - PCI HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, ARISING OUT OF OR IN - CONNECTION WITH THIS AGREEMENT, ON ANY THEORY OF LIABILITY, RESULTING FROM: (1) THE - USE OR INABILITY TO USE THE APPLICATION OR ANY OF ITS ADVERTISED FEATURES; (2) - UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; OR (3) ANY OTHER - MATTER RELATED TO THE APPLICATION, WHETHER BASED ON WARRANTY, COPYRIGHT, CONTRACT, - TORT (INCLUDING NEGLIGENCE), OR ANY OTHER LEGAL THEORY. THE FOREGOING CAP ON LIABILITY - SHALL NOT APPLY TO LIABILITY OF A PCI PARTY FOR (A) DEATH OR PERSONAL INJURY CAUSED BY - A PCI PARTY’S NEGLIGENCE; OR FOR (B) ANY INJURY CAUSED BY A PCI PARTY’S FRAUD OR - FRAUDULENT MISREPRESENTATION. - -
              2. Cap on Liability. TO THE MAXIMUM EXTENT PERMITTED BY LAW, - NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR - ANY DAMAGES ARISING FROM OR RELATED TO THESE TERMS OF USE (FOR ANY CAUSE WHATSOEVER - AND REGARDLESS OF THE FORM OF THE ACTION), WILL AT ALL TIMES BE LIMITED TO A MAXIMUM - OF FIFTY US DOLLARS (U.S. $50). THE EXISTENCE OF MORE THAN ONE CLAIM WILL NOT ENLARGE - THIS LIMIT. YOU AGREE THAT OUR SUPPLIERS WILL HAVE NO LIABILITY OF ANY KIND ARISING - FROM OR RELATING TO THESE TERMS OF USE. - -
              3. Your Data. EXCEPT FOR PCI’S OBLIGATIONS TO PROTECT YOUR PERSONAL - DATA AS SET FORTH IN THE PCI’S PRIVACY POLICY, PCI ASSUMES NO RESPONSIBILITY FOR THE - TIMELINESS, DELETION, MIS-DELIVERY OR FAILURE TO STORE ANY CONTENT (INCLUDING YOUR - LOCATION HISTORY), USER COMMUNICATIONS OR PERSONALIZATION SETTINGS. - -
              4. Basis of the Bargain. THE LIMITATIONS OF DAMAGES SET FORTH ABOVE - ARE FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN PCI AND YOU. -
              5. -
              - -
            10. REMEDIES. -
                - -
              1. - Violations. If PCI becomes aware of any possible violations by you of - this Agreement, PCI reserves the right to investigate such violations. If, as a result - of the investigation, PCI believes that criminal activity has occurred, PCI reserves - the right to refer the matter to, and to cooperate with, any and all applicable legal - authorities. PCI is entitled, except to the extent prohibited by applicable law, to - disclose any information or materials you provide to PCI in connection with your use - of the Application, to (a) comply with applicable laws, legal process or governmental - request; (b) enforce these Terms of Use, (c) respond to your requests for customer - service, or (d) protect the rights, property or personal safety of PCI or the public, - and all enforcement or other government officials, as PCI, in its sole discretion - believes to be necessary or appropriate. -
              2. -
              - -
            11. TERM AND TERMINATION. -
                - -
              1. - Term. This Agreement commences on the date when you accept them (as - described in the preamble above) and remain in full force and effect while you use the - Application, unless terminated earlier in accordance with this Agreement. - -
              2. Prior Use. Notwithstanding the foregoing, you hereby acknowledge - and agree that this Agreement commenced on the earlier to occur of (a) the date you - first used the Application or (b) the date you accepted this Agreement and will remain - in full force and effect while you use the Application, unless earlier terminated in - accordance with this Agreement. - -
              3. Termination by You. If you want to terminate this Agreement, you - may do so by deleting the Application from your mobile device. - -
              4. Effect of Termination. Termination of this Agreement requires you - to delete the Application and cease all use of it. All provisions of this Agreement - which by their nature should survive, shall survive termination, including without - limitation, ownership provisions, warranty disclaimers, and limitation of liability. -
              5. -
              - -
            12. INTERNATIONAL USERS. The Application is intended solely for use in the - United States of America. PCI makes no representations that the Application is functional - in other locations. Those who access or use the Application from other countries do so at - their own risk and are responsible for use in compliance with local law. - -
            13. DISPUTE RESOLUTION. Please read the following arbitration agreement in this - Section (“Arbitration Agreement”) carefully. It requires U.S. users to - arbitrate disputes with PCI and limits the manner in which you can seek relief from - us. -
                - -
              1. - Applicability of Arbitration Agreement. You agree that any - dispute, claim, or request for relief relating in any way to your access or use of the - Application or to any aspect of your relationship with PCI, will be resolved by - binding arbitration, rather than in court, except that (1) you may assert claims or - seek relief in small claims court if your claims qualify,; and (2) you or PCI may seek - equitable relief in court for infringement or other misuse of intellectual property - rights (such as trademarks, trade dress, domain names, trade secrets, copyrights, and - patents). This Arbitration Agreement shall apply, without limitation, to all - disputes or claims and requests for relief that arose or were asserted before the - effective date of this Agreement or any prior version of this Agreement. - -
              2. Arbitration Rules and Forum. The Federal Arbitration Act - governs the interpretation and enforcement of this Arbitration Agreement. To begin an - arbitration proceeding, you must send a letter requesting arbitration and describing - your dispute or claim or request for relief to our registered agent Samuel Hoff c/o - Pierce & Mandell, P.C. 11 Beacon Street Suite 800, Boston MA 02108. The arbitration - will be conducted by JAMS, an established alternative dispute resolution - provider. Disputes involving claims, counterclaims, or request for relief under - $250,000, not inclusive of attorneys’ fees and interest, shall be subject to JAMS’s - most current version of the Streamlined Arbitration Rules and procedures available at - http://www.jamsadr.com/rules-streamlined-arbitration/; all other disputes shall be - subject to JAMS’s most current version of the Comprehensive Arbitration Rules and - Procedures, available at http://www.jamsadr.com/rules-comprehensive-arbitration/. - JAMS’s rules are also available at www.jamsadr.com or by calling JAMS at 800-352-5267. - If JAMS is not available to arbitrate, the parties will select an alternative arbitral - forum. If the arbitrator finds that you cannot afford to pay JAMS’s filing, - administrative, hearing and/or other fees and cannot obtain a waiver from JAMS, PCI - will pay them for you. In addition, PCI will reimburse all such JAMS’s filing, - administrative, hearing and/or other fees for disputes, claims, or requests for relief - totaling less than $10,000 unless the arbitrator determines the claims are frivolous. -

                - You may choose to have the arbitration conducted by telephone, based on written - submissions, or at another mutually agreed location. Any judgment on the award - rendered by the arbitrator may be entered in any court of competent jurisdiction. -

                -
                  - -
                1. - Authority of Arbitrator. The arbitrator shall have exclusive - authority to (a) determine the scope and enforceability of this Arbitration - Agreement and (b) resolve any dispute related to the interpretation, - applicability, enforceability or formation of this Arbitration Agreement - including, but not limited to, any assertion that all or any part of this - Arbitration Agreement is void or voidable. The arbitration will decide the rights - and liabilities, if any, of you and PCI. The arbitration proceeding will not be - consolidated with any other matters or joined with any other cases or parties. - The arbitrator shall have the authority to grant motions dispositive of all or - part of any claim. The arbitrator shall have the authority to award monetary - damages and to grant any non-monetary remedy or relief available to an individual - under applicable law, the arbitral forum’s rules, and this Agreement (including - the Arbitration Agreement). The arbitrator shall issue a written award and - statement of decision describing the essential findings and conclusions on which - the award is based, including the calculation of any damages awarded. The - arbitrator has the same authority to award relief on an individual basis that a - judge in a court of law would have. The award of the arbitrator is final and - binding upon you and us. - -
                2. Waiver of Jury Trial. YOU AND PCI HEREBY WAIVE ANY - CONSTITUTIONAL AND STATUTORY RIGHTS TO SUE IN COURT AND HAVE A TRIAL IN FRONT OF A - JUDGE OR A JURY. You and PCI are instead electing that all disputes, claims, or - requests for relief shall be resolved by arbitration under this Arbitration - Agreement, except as specified in Section 13.1 above. An arbitrator can award on - an individual basis the same damages and relief as a court and must follow this - Agreement as a court would. However, there is no judge or jury in arbitration, and - court review of an arbitration award is subject to very limited review. - -
                3. Waiver of Class or Other Non-Individualized Relief. ALL - DISPUTES, CLAIMS, AND REQUESTS FOR RELIEF WITHIN THE SCOPE OF THIS ARBITRATION - AGREEMENT MUST BE ARBITRATED ON AN INDIVIDUAL BASIS AND NOT ON A CLASS OR - COLLECTIVE BASIS, ONLY INDIVIDUAL RELIEF IS AVAILABLE, AND CLAIMS OF MORE THAN ONE - CUSTOMER OR USER CANNOT BE ARBITRATED OR CONSOLIDATED WITH THOSE OF ANY OTHER - CUSTOMER OR USER. If a decision is issued stating that applicable law precludes - enforcement of any of this subsection’s limitations as to a given dispute, claim, - or request for relief, then such aspect must be severed from the arbitration and - brought into the State or Federal Courts located in the Commonwealth of - Massachusetts. All other disputes, claims, or requests for relief shall be - arbitrated. - -
                4. 30-Day Right to Opt Out. You have the right to opt out of the - provisions of this Arbitration Agreement by sending written notice of your - decision to opt out to: legal@pathcheck.org, within 30 days after first becoming - subject to this Arbitration Agreement. Your notice must include your name and - address, your email address, and an unequivocal statement that you want to opt out - of this Arbitration Agreement. If you opt out of this Arbitration Agreement, all - other parts of this Agreement will continue to apply to you. Opting out of this - Arbitration Agreement has no effect on any other arbitration agreements that you - may currently have, or may enter in the future, with us. - -
                5. Severability. Except as provided in subsection 13.5, - if any part or parts of this Arbitration Agreement are found under the law to be - invalid or unenforceable, then such specific part or parts shall be of no force - and effect and shall be severed and the remainder of the Arbitration Agreement - shall continue in full force and effect. - -
                6. Survival of Agreement. This Arbitration Agreement will - survive the termination of your relationship with PCI. - -
                7. Modification. Notwithstanding any provision in - this Agreement to the contrary, we agree that if PCI makes any future material - change to this Arbitration Agreement, you may reject that change within thirty - (30) days of such change becoming effective by writing PCI at the following - address: Path Check, Inc. PO Box 441621, Somerville MA 02144 -
                8. -
                - -
              3. GENERAL PROVISIONS. -
                  - -
                1. - Electronic Communications. The communications between you and PCI - may take place via electronic means, whether you send PCI e-mails, or whether PCI - posts notices in the Application or through updates made to the Application in - accordance with this Terms of Use or communicates with you via e-mail. For - contractual purposes, you (a) consent to receive communications from PCI in an - electronic form; and (b) agree that all terms and conditions, agreements, notices, - disclosures, and other communications that PCI provides to you electronically - satisfy any legal requirement that such communications would satisfy if it were to - be in writing. The foregoing does not affect your statutory rights, including but - not limited to the Electronic Signatures in Global and National Commerce Act at 15 - U.S.C. §7001 et seq. (“E-Sign”). - -
                2. Release. You hereby release the PCI Parties and their - successors from claims, demands, any and all losses, damages, rights, and actions - of any kind, including personal injuries, death, and property damage, that is - either directly or indirectly related to or arises from any interactions with or - conduct of operators of Third Party Safe Places Web Apps, healthcare providers, - governmental agencies, of any kind arising in connection with or as a result of - this Agreement or your use of the Application. If you are a California resident, - you hereby waive California Civil Code Section 1542, which states, “A general - release does not extend to claims that the creditor or releasing party does not - know or suspect to exist in his or her favor at the time of executing the release - and that, if known by him or her, would have materially affected his or her - settlement with the debtor or released party.” The foregoing release does not - apply to any claims, demands, or any losses, damages, rights and actions of any - kind, including personal injuries, death or property damage for any unconscionable - commercial practice by a PCI Party or for such party’s fraud, deception, false, - promise, misrepresentation or concealment, suppression or omission of any material - fact in connection with the Application provided hereunder. - -
                3. Assignment. This Agreement, and your rights and obligations - hereunder, may not be assigned, subcontracted, delegated or otherwise transferred - by you without PCI’s prior written consent, and any attempted assignment, - subcontract, delegation, or transfer in violation of the foregoing will be null - and void. - -
                4. Force Majeure. PCI shall not be liable for any delay or failure - to perform resulting from causes outside its reasonable control, including, but - not limited to, acts of God, war, terrorism, riots, embargos, acts of civil or - military authorities, fire, floods, accidents, strikes or shortages of - transportation facilities, fuel, energy, labor or materials. - -
                5. Questions, Complaints, Claims. If you have any questions, - complaints or claims with respect to the Application, please contact us at: - support@pathcheck.org. - -
                6. Exclusive Venue. To the extent the parties are permitted under - this Agreement to initiate litigation in a court, both you and PCI agree that all - claims and disputes arising out of or relating to this Agreement will be litigated - exclusively in the state or federal courts located in Boston, Massachusetts. - -
                7. Governing Law THE TERMS AND ANY ACTION RELATED THERETO WILL BE - GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF - MASSACHUSETTS, CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT - TO ANY PRINCIPLES THAT PROVIDE FOR THE APPLICATION OF THE LAW OF ANOTHER - JURISDICTION. THE UNITED NATIONS CONVENTION ON CONTRACTS FOR THE INTERNATIONAL - SALE OF GOODS DOES NOT APPLY TO THIS AGREEMENT. - -
                8. Choice of Language. It is the express wish of the parties that - this Agreement and all related documents have been drawn up in English. - -
                9. Notice. You may give notice to PCI at the following address: - Path Check, Inc. PO Box 441621, Somerville MA 02144. Such notice shall be deemed - given when received by PCI by letter delivered by nationally recognized overnight - delivery service or first class postage prepaid mail at the above address. - -
                10. Waiver. Any waiver or failure to enforce any provision of this - Agreement on one occasion will not be deemed a waiver of any other provision or of - such provision on any other occasion. - -
                11. Severability. If any portion of this Agreement is held invalid - or unenforceable, that portion shall be construed in a manner to reflect, as - nearly as possible, the original intention of the parties, and the remaining - portions shall remain in full force and effect. - -
                12. Export Control. You may not use, export, import, or transfer - the Application except as authorized by U.S. law, the laws of the jurisdiction in - which you obtained PCI Properties, and any other applicable laws. In particular, - but without limitation, the Application may not be exported or re-exported (a) - into any United States embargoed countries, or (b) to anyone on the U.S. Treasury - Department’s list of Specially Designated Nationals or the U.S. Department of - Commerce’s Denied Person’s List or Entity List. By using the Application, you - represent and warrant that (y) you are not located in a country that is subject to - a U.S. Government embargo, or that has been designated by the U.S. Government as a - “terrorist supporting” country and (z) you are not listed on any U.S. Government - list of prohibited or restricted parties. You also will not use the Application - for any purpose prohibited by U.S. law, including the development, design, - manufacture or production of missiles, nuclear, chemical or biological weapons. - You acknowledge and agree that products, services or technology provided by PCI - are subject to the export control laws and regulations of the United States. You - shall comply with these laws and regulations and shall not, without prior U.S. - government authorization, export, re-export, or transfer PCI products, services or - technology, either directly or indirectly, to any country in violation of such - laws and regulations. - -
                13. Accessing and Downloading the Application from iTunes. The - following applies to any App Store Sourced Application accessed through or - downloaded from the Apple App Store: -
                    - -
                  1. - You acknowledge and agree that (i) this Agreement is concluded between you and - PCI only, and not Apple, and (ii) PCI, not Apple, is solely responsible for - the App Store Sourced Application and content thereof. Your use of the App - Store Sourced Application must comply with the App Store Terms of Service. - -
                  2. You acknowledge that Apple has no obligation whatsoever to furnish any - maintenance and support services with respect to the App Store Sourced - Application. - -
                  3. In the event of any failure of the App Store Sourced Application to conform - to any applicable warranty, you may notify Apple, and Apple will refund the - purchase price for the App Store Sourced Application to you and to the maximum - extent permitted by applicable law, Apple will have no other warranty - obligation whatsoever with respect to the App Store Sourced Application. As - between PCI and Apple, any other claims, losses, liabilities, damages, costs - or expenses attributable to any failure to conform to any warranty will be the - sole responsibility of PCI. - -
                  4. You and PCI acknowledge that, as between PCI and Apple, Apple is not - responsible for addressing any claims you have or any claims of any third - party relating to the App Store Sourced Application or your possession and use - of the App Store Sourced Application, including, but not limited to: (i) - product liability claims; (ii) any claim that the App Store Sourced - Application fails to conform to any applicable legal or regulatory - requirement; and (iii) claims arising under consumer protection or similar - legislation. - -
                  5. You and PCI acknowledge that, in the event of any third-party claim that the - App Store Sourced Application or your possession and use of that App Store - Sourced Application infringes that third party’s intellectual property rights, - as between PCI and Apple, PCI, not Apple, will be solely responsible for the - investigation, defense, settlement and discharge of any such intellectual - property infringement claim to the extent required by this Agreement. - -
                  6. You and PCI acknowledge and agree that Apple, and Apple’s subsidiaries, are - third-party beneficiaries of this Agreement as related to your license of the - App Store Sourced Application, and that, upon your acceptance of the terms and - conditions of this Agreement, Apple will have the right (and will be deemed to - have accepted the right) to enforce this Agreement as related to your license - of the App Store Sourced Application against you as a third-party beneficiary - thereof. - -
                  7. Without limiting any other terms of this Agreement, you must comply with all - applicable third-party terms of agreement when using the App Store Sourced - Application. -
                  8. -
                  - -
                14. Consumer Complaints. In accordance with California Civil Code - §1789.3, you may report complaints to the Complaint Assistance Unit of the - Division of Consumer Services of the California Department of Consumer Affairs by - contacting them in writing at 1625 North Market Blvd., Suite N 112, Sacramento, CA - 95834, or by telephone at (800) 952-5210. - -
                15. Entire Agreement. This Agreement is the final, complete and - exclusive agreement of the parties with respect to the subject matter hereof and - supersedes and merges all prior discussions between the parties with respect to - such subject matter. +

                  Terms of Use

                  +

                  Last Updated Date: April 23, 2020

                  +

                  PLEASE READ THIS TERMS OF USE AGREEMENT (THE “TERMS + OF USE” OR “AGREEMENT” + ) CAREFULLY. THE SAFE PATH MOBILE APPLICATION (THE “APPLICATION” + ), AND THE FEATURES AND INFORMATION IN IT ARE + CONTROLLED BY PATH CHECK, INC. (“PCI”). + THESE TERMS OF USE GOVERN THE USE OF THE APPLICATION AND APPLY TO ALL USERS USING THE APPLICATION IN + ANY WAY, INCLUDING THE FEATURES THEREIN. BY CLICKING ON THE “I ACCEPT” BUTTON AND/OR DOWNLOADING + THE APPLICATION, YOU REPRESENT THAT (1) YOU HAVE READ, UNDERSTAND, AND AGREE TO BE BOUND BY THE TERMS OF + USE, (2) YOU ARE OF LEGAL AGE TO FORM A BINDING CONTRACT WITH PCI, AND (3) YOU HAVE THE AUTHORITY TO ENTER + INTO THE TERMS OF USE. IF YOU DO NOT AGREE TO BE BOUND BY THE TERMS OF USE, + YOU MAY NOT ACCESS OR USE THE APPLICATION.

                  +

                  PLEASE BE AWARE THAT SECTION 13 OF THIS AGREEMENT, BELOW, CONTAINS PROVISIONS + GOVERNING HOW DISPUTES THAT YOU AND WE HAVE AGAINST EACH OTHER ARE RESOLVED, INCLUDING, WITHOUT LIMITATION, + ANY DISPUTES THAT AROSE OR WERE ASSERTED PRIOR TO THE EFFECTIVE DATE OF THIS AGREEMENT. IN PARTICULAR, IT + CONTAINS AN ARBITRATION AGREEMENT WHICH WILL, WITH LIMITED EXCEPTIONS, REQUIRE DISPUTES BETWEEN US TO BE + SUBMITTED TO BINDING AND FINAL ARBITRATION. UNLESS YOU OPT OUT OF THE ARBITRATION AGREEMENT: (1) YOU + WILL ONLY BE PERMITTED TO PURSUE DISPUTES OR CLAIMS AND SEEK RELIEF AGAINST US ON AN INDIVIDUAL BASIS, NOT + AS A PLAINTIFF OR CLASS MEMBER IN ANY CLASS OR REPRESENTATIVE ACTION OR PROCEEDING; AND (2) YOU ARE WAIVING + YOUR RIGHT TO PURSUE DISPUTES OR CLAIMS AND SEEK RELIEF IN A COURT OF LAW AND TO HAVE A JURY TRIAL. +

                  +

                  ANY DISPUTE, CLAIM OR REQUEST FOR RELIEF RELATING IN ANY WAY TO YOUR USE OF THE + APPLICATION WILL BE GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF MASSACHUSETTS, + CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT TO ANY PRINCIPLES THAT PROVIDE FOR THE + APPLICATION OF THE LAW OF ANY OTHER JURISDICTION. THE UNITED NATIONS CONVENTION ON CONTRACTS FOR THE + INTERNATIONAL SALE OF GOODS IS EXPRESSLY EXCLUDED FROM THIS AGREEMENT.

                  +

                  PLEASE NOTE THAT THIS AGREEMENT IS SUBJECT TO CHANGE BY PCI + IN ITS SOLE DISCRETION AT ANY TIME. When changes are made, PCI will make a new + copy of the Terms of Use available within the Application. We will also update the “Last + Updated” date at the top of the Terms of Use. Your continued use of the Application after a + change to the Terms of Use is made constitutes acceptance. PLEASE REGULARLY CHECK THE APPLICATION TO + VIEW THE MOST CURRENT TERMS.

                  +

                  1. USE OF THE APPLICATION. +

                  +

                  1.1 Definitions. As used in + these Terms of Use, the following definitions apply:

                  +

                  “App User” means an + individual natural person who has downloaded the Application under these Terms of Use.

                  +

                  “Location History” + means location data collected from an App User’s mobile device leveraging GPS, Bluetooth and/or other + features or software, that the App User elects to have recorded and stored within the Application installed + on the App User’s mobile device.

                  +

                  “Publicly Available Safe Places Data + ” means anonymized maps of public places where individuals diagnosed with Covid-19 have + visited and data files of times they visited such places, as compiled and created by a third party, and made + available via such third party’s Third Party Safe Places Web App.

                  +

                  “Safe Places Software” + means open-source software, available at https://github.com/tripleblindmarket/safe-places + , that facilitates contact tracing and related tasks, and is designed for use by third parties, + such as government agencies.

                  +

                  “Third Party Safe Places Web App + ” means a web-based application owned and operated by a third party, such as a government + agency, that employs Safe Places Software to, among other things, publish Publicly Available Safe Places + Data.

                  +

                  1.2 Application Features and Functionality. + The Application is designed to enable App Users, at their + option: (a) to record their Location History and to store such data locally in the + Application downloaded to their mobile device, (b) to access Publicly Available Safe Places Data from one or + more Third Party Safe Places Web Apps and download it to the Safe Paths App downloaded on their mobile + device, and (c) to share their stored Location History with third parties that they choose. +

                  +

                  1.3 Application License. The + Application and the information and content available therein are protected by copyright laws throughout the + world. Subject to your compliance with this Agreement, PCI grants you a limited non-exclusive, + non-transferable, non-sublicensable, revocable license to download, install and use a copy of the + Application on a single mobile device or computer that you own or control and to run such copy of the + Application solely for your own personal or internal business purposes. Certain components or + libraries included in or bundled with the Application constitute open source software and are licensed under + open source licenses. To the extent required by such open source licenses, the terms of such licenses + will apply in lieu of the terms of this Section 1.3, solely with respect to those components or libraries + that are licensed under such open source licenses. For a copy of such open source licenses, visit + https://github.com/tripleblindmarket/covid-safe-paths/blob/develop/LICENSE + . Furthermore, with respect to any Application accessed through or downloaded from the + Apple App Store (an“App Store Sourced Application” + ), you will only use the App Store Sourced Application (a) on an Apple-branded product that runs + the iOS (Apple’s proprietary operating system) and (b) as permitted by the “Usage Rules” + set forth in the Apple App Store Terms of Service. Notwithstanding the first sentence in this section, with + respect to any Application accessed through or downloaded from the Google Play store (a “ + Google Play Sourced Application”), you may have additional + license rights with respect to use of the Application on a shared basis within your designated family + group.

                  +

                  1.4 Updates. + You understand that to keep the Application most useful for App Users and to accommodate bug fixes and other + technological changes, PCI may make updates to the Application after you download the Application. + These updates will be made available to App Users from the third party from whom you received the + Application license, e.g., the Apple App Store or Google Play (each, an “App Store + ”). You will have the ability, at your + option, to download any updates to the Application that we make freely available through such channels. + We do not require you to install any updates in order for you to continue using the Application after + we make such updates available. This is because we do not retain any information about you when you + download the Application. Therefore, we will not know whether you have installed any such updates. + Accordingly, you acknowledge and agree that if you elect NOT to install available updates, the + Application may not operate in accordance with publicly available documentation regarding the features and + functionality of the Application, which documentation may be updated after the time you originally + downloaded it. In addition, you may need to update third-party software from time to time in order to + use the Application. Any updates issued by PCI and installed by you shall be deemed the Application + and shall be governed by this Safe Places EULA or any subsequent end user license agreement accompanying the + update.

                  +

                  1.5 Necessary Equipment and Software. + You must provide all equipment and software necessary to download and use the Application + and to connect to any third party services or sites, including but not limited to, a mobile device that is + suitable to connect with and use the Application. You are solely responsible for any fees, including + Internet connection or mobile fees, that you incur when accessing the Application.

                  +

                  1.6 Responsibility for Location History and Other Data + Collected and Stored by You. PCI has no access to the Location History or any + other data you collect, receive and store in the installed Application or otherwise on your mobile device. + Therefore, PCI has no responsibility or liability for the deletion or accuracy of the Location History + or other data you collect, receive or store in the Application or the failure to store, transmit or receive + transmission of Location History or other data; or the security, privacy, storage, or transmission of other + communications originating with or involving use of the Application.

                  +

                  2. OWNERSHIP.

                  +

                  2.1 Application. Except with + respect to your Location History and other data you may collect, retrieve from third parties and store in + the Application on your device, you agree that PCI and its suppliers own all rights, title and interest in + and to the Application. You will not remove, alter or obscure any copyright, trademark, service mark + or other proprietary rights notices incorporated in or accompanying the Application.

                  +

                  2.2 Trademarks.COVID Safe + Paths and all related graphics, logos, service marks and trade names used on or in connection with the + Application or in connection therewith are the trademarks of PCI and may not be used without permission in + connection with your, or any third-party, products or services. Other trademarks, service marks and + trade names that may appear on or in the Application are the property of their respective owners.

                  +

                  3. COLLECTION AND USE OF YOUR PERSONAL + INFORMATION. You acknowledge that the Application + may collect personal information about you (through your choice to store information in the Application or + through automatic technology tools), including your Location History. You acknowledge that the + Application may provide you with opportunities to share personal information about yourself, including your + Location History with others. All personal information in connection with this Application is subject to our + Privacy Policy. By downloading, installing, using, and providing personal information to or through this + Application, you consent to all actions taken by us with respect to your personal information in compliance + with the Privacy Policy. For some features of the Application, specific consent is required. In + addition, you may choose to disclose, through other means not associated with the Application, any part of + your personal information to family members, doctors, health care providers, governmental agencies, or other + individuals or entities. We recommend that you make your choices regarding sharing your personal + information, through the Application or otherwise, carefully. We will have no liability for any + consequences that may result because you have released or shared information, through the Application or + otherwise, with a third party.

                  +

                  4. FEEDBACK. If you provide + PCI with any ideas, suggestions, documents, and/or proposals (“Feedback” + ) via email or another means, you do so at your + own risk and you acknowledge and agree that PCI has no obligations (including without limitation obligations + of confidentiality) with respect to such Feedback. You represent and warrant that you have all rights + necessary to submit the Feedback. You hereby grant to PCI a fully paid, royalty-free, perpetual, + irrevocable, worldwide, non-exclusive, and fully sublicensable right and license to use, reproduce, perform, + display, distribute, adapt, modify, re-format, create derivative works of, and otherwise commercially or + non-commercially exploit in any manner, any and all Feedback, and to sublicense the foregoing rights, in + connection with the operation and maintenance of the Application, any other PCI products or services, or + PCI’s business.

                  +

                  5. APP STORES. + You acknowledge and agree that the availability of the Application is + dependent on the App Store from whom you received the Application license. You acknowledge that this + Agreement is between you and PCI and not with the App Store. PCI, not the App Store, is solely + responsible for the Application, including the content PCI makes available therein, and the maintenance, + support services, and warranty therefor, and addressing any claims relating thereto (e.g., product + liability, legal compliance or intellectual property infringement). In order to use the Application, + you must have access to a wireless network, and you agree to pay all fees associated with such access. + You also agree to pay all fees (if any) charged by the App Store in connection with the Application. + You agree to comply with, and your license to use the Application is conditioned upon your compliance + with all terms of agreement imposed by the applicable App Store when using the Application. You acknowledge + that the App Store (and its subsidiaries) are third-party beneficiaries of this Agreement and will have the + right to enforce it.

                  +

                  6. FEES. No fees shall be + payable under this Agreement for the rights granted under this Agreement. You acknowledge and agree that + this fee arrangement is made in consideration for the mutual covenants set forth in this Agreement, + including your obligations hereunder, and the disclaimers, exclusions, and limitations of liability set + forth herein.

                  +

                  7. INDEMNIFICATION. You + agree to indemnify and hold PCI, its parents, subsidiaries, affiliates, officers, employees, volunteers, + agents, partners, suppliers, and licensors (each, a “PCI Party + ” and collectively, the “PCI Parties” + ) harmless from any losses, costs, liabilities and expenses (including reasonable + attorneys’ fees) relating to or arising out of any and all of the following: (a) your use of, or + inability to use the Application; (b) your violation of this Agreement; (c) your violation of any rights of + another party; or (d) your violation of any applicable laws, rules or regulations. PCI reserves the + right, at its own cost, to assume the exclusive defense and control of any matter otherwise subject to + indemnification by you, in which event you will fully cooperate with PCI in asserting any available + defenses. This provision does not require you to indemnify any of the PCI Parties for any + unconscionable commercial practice by such party or for such party’s fraud, deception, false promise, + misrepresentation or concealment, suppression or omission of any material fact in connection with the + Application provided hereunder. You agree that the provisions in this section will survive any termination + of this Agreement and/or your use of or access to Application.

                  +

                  8. DISCLAIMER OF WARRANTIES AND CONDITIONS.

                  +

                  8.1 As Is.YOU EXPRESSLY + UNDERSTAND AND AGREE THAT TO THE EXTENT PERMITTED BY APPLICABLE LAW, YOUR USE OF THE APPLICATION IS AT YOUR + SOLE RISK, AND THE APPLICATION IS PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS, + WITH ALL FAULTS. THE PCI PARTIES EXPRESSLY DISCLAIM ALL WARRANTIES, REPRESENTATIONS, AND CONDITIONS OF + ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARISING FROM USE OF THE APPLICATION. +

                  +

                  (a) THE PCI PARTIES MAKE NO WARRANTY, REPRESENTATION OR + CONDITION THAT: (1) THE APPLICATION OR ITS FEATURES WILL MEET YOUR REQUIREMENTS; (2) YOUR USE OF THE + APPLICATION WILL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; OR (3) THE RESULTS THAT MAY BE OBTAINED + FROM USE OF THE APPLICATION WILL BE ACCURATE OR RELIABLE.

                  +

                  (b) ANY CONTENT DOWNLOADED FROM OR OTHERWISE ACCESSED THROUGH + THE APPLICATION IS ACCESSED AT YOUR OWN RISK, AND YOU SHALL BE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR + PROPERTY, INCLUDING, BUT NOT LIMITED TO, YOUR COMPUTER SYSTEM AND ANY DEVICE YOU USE TO ACCESS THE + APPLICATION, OR ANY OTHER LOSS THAT RESULTS FROM ACCESSING SUCH CONTENT.

                  +

                  (c) THE AVAILABILITY OF THE APPLICATION AND ITS FEATURES MAY + BE SUBJECT TO DELAYS, CANCELLATIONS AND OTHER DISRUPTIONS. PCI MAKES NO WARRANTY, REPRESENTATION OR + CONDITION WITH RESPECT TO THE APPLICATION OR ITS FEATURES, INCLUDING BUT NOT LIMITED TO, THE QUALITY, + EFFECTIVENESS, REPUTATION AND OTHER CHARACTERISTICS THEREOF.

                  +

                  (d) NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, + OBTAINED FROM PCI OR THROUGH THE APPLICATION WILL CREATE ANY WARRANTY NOT EXPRESSLY MADE HEREIN.

                  +

                  (e) FROM TIME TO TIME, PCI MAY OFFER NEW “BETA” + FEATURES OR TOOLS WITH WHICH ITS USERS MAY EXPERIMENT. SUCH FEATURES OR TOOLS ARE OFFERED SOLELY FOR + EXPERIMENTAL PURPOSES AND WITHOUT ANY WARRANTY OF ANY KIND, AND MAY BE MODIFIED OR DISCONTINUED AT + PCI’S SOLE DISCRETION. THE PROVISIONS OF THIS SECTION APPLY WITH FULL FORCE TO SUCH FEATURES OR + TOOLS.

                  +

                  8.2 NOT INTENDED AS MEDICAL ADVICE. + YOU ACKNOWLEDGE THAT THE INFORMATION IN THE APPLICATION IS PROVIDED FOR GENERAL + INFORMATIONAL PURPOSES ONLY. IT IS NOT INTENDED AS MEDICAL ADVICE OF ANY KIND NOR IS IT INTENDED TO + DIAGNOSE, TREAT, CURE OR PREVENT ANY DISEASE OR MEDICAL CONDITION. THE INFORMATION PRESENTED IN THE + APPLICATION SHOULD NOT BE INTERPRETED OR CONSTRUED IN ANY WAY AS A REPLACEMENT OR SUBSTITUTE FOR MEDICAL + ADVICE PROVIDED BY YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER. YOU SHOULD NOT DISREGARD, AVOID + OR DELAY OBTAINING MEDICAL ADVICE OR TREATMENT FROM YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER DUE + TO ANY INFORMATION PROVIDED IN THE APPLICATION. UNDER NO CIRCUMSTANCES SHOULD YOU ALTER YOUR EXISTING + MEDICAL TREATMENT, MEDICATION REGIMEN, OR ANY OTHER RELATED HEALTHCARE ACTIVITIES BASED ON ANY INFORMATION + PROVIDED IN THE APPLICATION. IT IS IMPORTANT FOR YOU TO DISCUSS YOUR TREATMENT OPTIONS, AND ANY QUESTIONS + THAT YOU MAY HAVE, WITH YOUR DOCTOR OR OTHER QUALIFIED HEALTHCARE PROVIDER.

                  +

                  8.3 In making the Application available for download, + PCI’s goal is to provide individuals with a useful tool for contact tracing. However, the + utility of the Application’s features is dependent upon a number of factors that are outside the + control of PCI, such as the reliability of GPS sensors, whether individuals are carrying their mobile + devices, as well as the accuracy, reliability, availability, effectiveness or correct use of + individual’s mobile devices and GPS sensors, all of which are used to create Location History, and for + any Publicly Available Safe Places Data that you may upload into your Application from third party source, + the accuracy and completeness of such data. Your Location History or other data may be unavailable, + inaccurate or incomplete. Use of the Application should not replace your good judgment and common + sense.

                  +

                  8.4 No Liability for Conduct of Third Parties. + YOU ACKNOWLEDGE AND AGREE THAT PCI PARTIES ARE NOT LIABLE, AND YOU AGREE NOT TO SEEK TO + HOLD PCI PARTIES LIABLE, FOR THE CONDUCT OR OMISSIONS OF THIRD PARTIES, INCLUDING THE ACTIONS OF ANY THIRD + PARTY OPERATING A THIRD PARTY SAFE PLACES WEB APP OR ANY GOVERNMENTAL AGENCY WITH WHICH YOU CHOOSE TO + INTERACT IN CONNECTION WITH YOUR USE OF THE APPLICATION, AND THAT THE RISK OF INJURY FROM SUCH THIRD PARTIES + RESTS ENTIRELY WITH YOU.

                  +

                  9. LIMITATION OF LIABILITY.

                  +

                  9.1 Disclaimer of Certain Damages. + YOU UNDERSTAND AND AGREE THAT IN NO EVENT SHALL THE PCI PARTIES BE LIABLE FOR ANY LOSS OF + PROFITS, PERSONAL INJURY, PROPERTY DAMAGE, WRONGFUL DEATH, REVENUE OR DATA, INDIRECT, INCIDENTAL, SPECIAL, + OR CONSEQUENTIAL DAMAGES, OR DAMAGES OR COSTS DUE TO LOSS OF PRODUCTION OR USE, BUSINESS INTERRUPTION, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, IN EACH CASE WHETHER OR NOT PCI HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES, ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT, ON ANY THEORY OF + LIABILITY, RESULTING FROM: (1) THE USE OR INABILITY TO USE THE APPLICATION OR ANY OF ITS ADVERTISED + FEATURES; (2) UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; OR (3) ANY OTHER + MATTER RELATED TO THE APPLICATION, WHETHER BASED ON WARRANTY, COPYRIGHT, CONTRACT, TORT (INCLUDING + NEGLIGENCE), OR ANY OTHER LEGAL THEORY. THE FOREGOING CAP ON LIABILITY SHALL NOT APPLY TO LIABILITY OF + A PCI PARTY FOR (A) DEATH OR PERSONAL INJURY CAUSED BY A PCI PARTY’S NEGLIGENCE; OR FOR (B) ANY INJURY + CAUSED BY A PCI PARTY’S FRAUD OR FRAUDULENT MISREPRESENTATION.

                  +

                  9.2 Cap on Liability. TO THE + MAXIMUM EXTENT PERMITTED BY LAW, NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO + YOU FOR ANY DAMAGES ARISING FROM OR RELATED TO THESE TERMS OF USE (FOR ANY CAUSE WHATSOEVER AND REGARDLESS + OF THE FORM OF THE ACTION), WILL AT ALL TIMES BE LIMITED TO A MAXIMUM OF FIFTY US DOLLARS (U.S. $50). THE + EXISTENCE OF MORE THAN ONE CLAIM WILL NOT ENLARGE THIS LIMIT. YOU AGREE THAT OUR SUPPLIERS WILL HAVE + NO LIABILITY OF ANY KIND ARISING FROM OR RELATING TO THESE TERMS OF USE.

                  +

                  9.3 Your Data. EXCEPT FOR + PCI’S OBLIGATIONS TO PROTECT YOUR PERSONAL DATA AS SET FORTH IN THE PCI’S PRIVACY POLICY, PCI + ASSUMES NO RESPONSIBILITY FOR THE TIMELINESS, DELETION, MIS-DELIVERY OR FAILURE TO STORE ANY CONTENT + (INCLUDING YOUR LOCATION HISTORY), USER COMMUNICATIONS OR PERSONALIZATION SETTINGS.

                  +

                  9.4 Basis of the Bargain. + THE LIMITATIONS OF DAMAGES SET FORTH ABOVE ARE FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN PCI + AND YOU.

                  +

                  10. REMEDIES.

                  +

                  10.1 Violations. If PCI + becomes aware of any possible violations by you of this Agreement, PCI reserves the right to investigate + such violations. If, as a result of the investigation, PCI believes that criminal activity has + occurred, PCI reserves the right to refer the matter to, and to cooperate with, any and all applicable legal + authorities. PCI is entitled, except to the extent prohibited by applicable law, to disclose any + information or materials you provide to PCI in connection with your use of the Application, to (a) comply + with applicable laws, legal process or governmental request; (b) enforce these Terms of Use, (c) respond to + your requests for customer service, or (d) protect the rights, property or personal safety of PCI or the + public, and all enforcement or other government officials, as PCI, in its sole discretion believes to be + necessary or appropriate.

                  +

                  11. TERM AND TERMINATION.

                  +

                  11.1 Term. This Agreement + commences on the date when you accept them (as described in the preamble above) and remain in full force and + effect while you use the Application, unless terminated earlier in accordance with this Agreement. +

                  +

                  11.2 Prior Use. + Notwithstanding the foregoing, you hereby acknowledge and agree that this Agreement + commenced on the earlier to occur of (a) the date you first used the Application or (b) the date you + accepted this Agreement and will remain in full force and effect while you use the Application, unless + earlier terminated in accordance with this Agreement.

                  +

                  11.3 Termination by You. If + you want to terminate this Agreement, you may do so by deleting the Application from your mobile device. +

                  +

                  11.4 Effect of Termination. + Termination of this Agreement requires you to delete the Application and cease all use of it. All + provisions of this Agreement which by their nature should survive, shall survive termination, including + without limitation, ownership provisions, warranty disclaimers, and limitation of liability.

                  +

                  12. INTERNATIONAL USERS. The + Application is intended solely for use in the United States of America. PCI makes no representations + that the Application is functional in other locations. Those who access or use the Application from + other countries do so at their own risk and are responsible for use in compliance with local law.

                  +

                  13. DISPUTE RESOLUTION. + Please read the following + arbitration agreement in this Section (“Arbitration Agreement + ”) carefully. It requires U.S. users to arbitrate disputes with PCI and limits the + manner in which you can seek relief from us.

                  +

                  13.1 Applicability of Arbitration + Agreement.You agree that any dispute, claim, or + request for relief relating in any way to your access or use of the Application or to any aspect of your + relationship with PCI, will be resolved by binding arbitration, rather than in court, except that (1) you + may assert claims or seek relief in small claims court if your claims qualify,; and (2) you or PCI may seek + equitable relief in court for infringement or other misuse of intellectual property rights (such as + trademarks, trade dress, domain names, trade secrets, copyrights, and patents). + This Arbitration Agreement shall apply, without limitation, to all disputes or claims and + requests for relief that arose or were asserted before the effective date of this Agreement or any prior + version of this Agreement.

                  +

                  13.2 Arbitration Rules and Forum + The Federal Arbitration Act governs the interpretation and + enforcement of this Arbitration Agreement. To begin an arbitration proceeding, you must send a letter + requesting arbitration and describing your dispute or claim or request for relief to our registered + agentSamuel Hoff c/o Pierce & Mandell, P.C. 11 + Beacon Street Suite 800, Boston MA 02108. The arbitration will be conducted by JAMS, an established + alternative dispute resolution provider.Disputes involving claims, counterclaims, or request for + relief under $250,000, not inclusive of attorneys’ fees and interest, shall be subject to JAMS’s + most current version of the Streamlined Arbitration Rules and procedures available at + http://www.jamsadr.com/rules-streamlined-arbitration/; all other disputes shall be subject to JAMS’s + most current version of the Comprehensive Arbitration Rules and Procedures, available at + http://www.jamsadr.com/rules-comprehensive-arbitration/. JAMS’s rules are also available at + www.jamsadr.com or by calling JAMS at 800-352-5267. If JAMS is not available to arbitrate, the parties + will select an alternative arbitral forum. If the arbitrator finds that you cannot afford to pay + JAMS’s filing, administrative, hearing and/or other fees and cannot obtain a waiver from JAMS, PCI + will pay them for you. In addition, PCI will reimburse all such JAMS’s filing, administrative, + hearing and/or other fees for disputes, claims, or requests for relief totaling less than $10,000 unless the + arbitrator determines the claims are frivolous.

                  +

                  You may choose to have the arbitration conducted by telephone, based on written + submissions, or at another mutually agreed location. Any judgment on the award rendered by the + arbitrator may be entered in any court of competent jurisdiction.

                  +

                  13.3 Authority of Arbitrator + . The arbitrator shall have exclusive authority to (a) determine the scope and + enforceability of this Arbitration Agreement and (b) resolve any dispute related to the interpretation, + applicability, enforceability or formation of this Arbitration Agreement including, but not limited to, any + assertion that all or any part of this Arbitration Agreement is void or voidable. The arbitration will + decide the rights and liabilities, if any, of you and PCI. The arbitration proceeding will not be + consolidated with any other matters or joined with any other cases or parties. The arbitrator shall + have the authority to grant motions dispositive of all or part of any claim. The arbitrator shall have the + authority to award monetary damages and to grant any non-monetary remedy or relief available to an + individual under applicable law, the arbitral forum’s rules, and this Agreement (including the + Arbitration Agreement). The arbitrator shall issue a written award and statement of decision describing the + essential findings and conclusions on which the award is based, including the calculation of any damages + awarded. The arbitrator has the same authority to award relief on an individual basis that a judge in + a court of law would have. The award of the arbitrator is final and binding upon you and us. +

                  +

                  13.4 Waiver of Jury Trial + . YOU AND PCI HEREBY WAIVE ANY CONSTITUTIONAL AND STATUTORY RIGHTS TO SUE IN COURT AND HAVE + A TRIAL IN FRONT OF A JUDGE OR A JURY. You and PCI are instead electing that all disputes, claims, or + requests for relief shall be resolved by arbitration under this Arbitration Agreement, except as specified + in Section 13.1 above. An arbitrator can award on an individual basis the same damages and relief as a + court and must follow this Agreement as a court would. However, there is no judge or jury in + arbitration, and court review of an arbitration award is subject to very limited review.

                  +

                  13.5 Waiver of Class or Other + Non-Individualized Relief. ALL DISPUTES, CLAIMS, AND REQUESTS FOR RELIEF + WITHIN THE SCOPE OF THIS ARBITRATION AGREEMENT MUST BE ARBITRATED ON AN INDIVIDUAL BASIS AND NOT ON A CLASS + OR COLLECTIVE BASIS, ONLY INDIVIDUAL RELIEF IS AVAILABLE, AND CLAIMS OF MORE THAN ONE CUSTOMER OR USER + CANNOT BE ARBITRATED OR CONSOLIDATED WITH THOSE OF ANY OTHER CUSTOMER OR USER. If a decision is issued + stating that applicable law precludes enforcement of any of this subsection’s limitations as to a + given dispute, claim, or request for relief, then such aspect must be severed from the arbitration and + brought into the State or Federal Courts located in the Commonwealth of Massachusetts. All other + disputes, claims, or requests for relief shall be arbitrated.

                  +

                  13.6 30-Day Right to Opt Out. + You have the right to opt out of the provisions of this Arbitration Agreement by sending written notice of + your decision to opt out to: legal@pathcheck.org, within 30 + days after first becoming subject to this Arbitration Agreement. Your notice must include your name + and address, your email address, and an unequivocal statement that you want to opt out of this Arbitration + Agreement. If you opt out of this Arbitration Agreement, all other parts of this Agreement will + continue to apply to you. Opting out of this Arbitration Agreement has no effect on any other + arbitration agreements that you may currently have, or may enter in the future, with us.

                  +

                  13.7 Severability. + Except as provided in subsection 13.5, if any part or parts of this Arbitration + Agreement are found under the law to be invalid or unenforceable, then such specific part or parts shall be + of no force and effect and shall be severed and the remainder of the Arbitration Agreement shall continue in + full force and effect.

                  +

                  13.8 Survival of Agreement. + This Arbitration Agreement will survive the termination of your relationship with + PCI.

                  +

                  13.9 Modification. + Notwithstanding any provision in this Agreement to the + contrary, we agree that if PCI makes any future material change to this Arbitration Agreement, you may + reject that change within thirty (30) days of such change becoming effective by writing + PCI at the following address: Path Check, Inc. PO Box 441621, Somerville MA 02144

                  +

                  14. GENERAL PROVISIONS.

                  +

                  14.1 Electronic Communications. + The communications between you and PCI may take place via electronic means, whether you + send PCI e-mails, or whether PCI posts notices in the Application or through updates made to the Application + in accordance with this Terms of Use or communicates with you via e-mail. For contractual purposes, + you (a) consent to receive communications from PCI in an electronic form; and (b) agree that all terms and + conditions, agreements, notices, disclosures, and other communications that PCI provides to you + electronically satisfy any legal requirement that such communications would satisfy if it were to be in + writing. The foregoing does not affect your statutory rights, including but not limited to the + Electronic Signatures in Global and National Commerce Act at 15 U.S.C. §7001 et seq. + (“E-Sign”).

                  +

                  14.2 Release. You hereby + release the PCI Parties and their successors from claims, demands, any and all losses, damages, rights, and + actions of any kind, including personal injuries, death, and property damage, that is either directly or + indirectly related to or arises from any interactions with or conduct of operators of Third Party Safe + Places Web Apps, healthcare providers, governmental agencies, of any kind arising in connection with or as a + result of this Agreement or your use of the Application. If you are a California resident, you hereby + waive California Civil Code Section 1542, which states, “A general release does not extend to claims + that the creditor or releasing party does not know or suspect to exist in his or her favor at the time of + executing the release and that, if known by him or her, would have materially affected his or her settlement + with the debtor or released party.” The foregoing release does not apply to any claims, demands, + or any losses, damages, rights and actions of any kind, including personal injuries, death or property + damage for any unconscionable commercial practice by a PCI Party or for such party’s fraud, deception, + false, promise, misrepresentation or concealment, suppression or omission of any material fact in connection + with the Application provided hereunder.

                  +

                  14.3 Assignment. This + Agreement, and your rights and obligations hereunder, may not be assigned, subcontracted, delegated or + otherwise transferred by you without PCI’s prior written consent, and any attempted assignment, + subcontract, delegation, or transfer in violation of the foregoing will be null and void.

                  +

                  14.4 Force Majeure. PCI + shall not be liable for any delay or failure to perform resulting from causes outside its reasonable + control, including, but not limited to, acts of God, war, terrorism, riots, embargos, acts of civil or + military authorities, fire, floods, accidents, strikes or shortages of transportation facilities, fuel, + energy, labor or materials.

                  +

                  14.5 Questions, Complaints, Claims. + If you have any questions, complaints or claims with respect to the Application, please + contact us at: support@pathcheck.org.

                  +

                  14.6 Exclusive Venue. To the + extent the parties are permitted under this Agreement to initiate litigation in a court, both you and PCI + agree that all claims and disputes arising out of or relating to this Agreement will be litigated + exclusively in the state or federal courts located in Boston, Massachusetts.

                  +

                  14.7 Governing Law THE TERMS AND + ANY ACTION RELATED THERETO WILL BE GOVERNED AND INTERPRETED BY AND UNDER THE LAWS OF THE COMMONWEALTH OF + MASSACHUSETTS, CONSISTENT WITH THE FEDERAL ARBITRATION ACT, WITHOUT GIVING EFFECT TO ANY PRINCIPLES THAT + PROVIDE FOR THE APPLICATION OF THE LAW OF ANOTHER JURISDICTION. THE UNITED NATIONS CONVENTION ON + CONTRACTS FOR THE INTERNATIONAL SALE OF GOODS DOES NOT APPLY TO THIS AGREEMENT.

                  +

                  14.8 Choice of Language. It + is the express wish of the parties that this Agreement and all related documents have been drawn up in + English.

                  +

                  14.9 Notice. You may give + notice to PCI at the following address:Path Check, Inc. PO Box 441621, + Somerville MA 02144. Such notice shall be deemed given when received by PCI by + letter delivered by nationally recognized overnight delivery service or first class postage prepaid mail at + the above address.

                  +

                  14.10 Waiver. Any waiver or + failure to enforce any provision of this Agreement on one occasion will not be deemed a waiver of any other + provision or of such provision on any other occasion.

                  +

                  14.11 Severability. If any + portion of this Agreement is held invalid or unenforceable, that portion shall be construed in a manner to + reflect, as nearly as possible, the original intention of the parties, and the remaining portions shall + remain in full force and effect.

                  +

                  14.12 Export Control. You + may not use, export, import, or transfer the Application except as authorized by U.S. law, the laws of the + jurisdiction in which you obtained PCI Properties, and any other applicable laws. In particular, but + without limitation, the Application may not be exported or re-exported (a) into any United States embargoed + countries, or (b) to anyone on the U.S. Treasury Department’s list of Specially Designated Nationals + or the U.S. Department of Commerce’s Denied Person’s List or Entity List. By using the + Application, you represent and warrant that (y) you are not located in a country that is subject to a U.S. + Government embargo, or that has been designated by the U.S. Government as a “terrorist + supporting” country and (z) you are not listed on any U.S. Government list of prohibited or restricted + parties. You also will not use the Application for any purpose prohibited by U.S. law, including the + development, design, manufacture or production of missiles, nuclear, chemical or biological weapons. + You acknowledge and agree that products, services or technology provided by PCI are subject to the + export control laws and regulations of the United States. You shall comply with these laws and + regulations and shall not, without prior U.S. government authorization, export, re-export, or transfer PCI + products, services or technology, either directly or indirectly, to any country in violation of such laws + and regulations.

                  +

                  14.13 Accessing and Downloading the Application from + iTunes. The following applies to any App Store Sourced Application accessed + through or downloaded from the Apple App Store:

                  +

                  (a) You acknowledge and agree that (i) this Agreement is + concluded between you and PCI only, and not Apple, and (ii) PCI, not Apple, is solely responsible for the + App Store Sourced Application and content thereof. Your use of the App Store Sourced Application must comply + with the App Store Terms of Service.

                  +

                  (b) You acknowledge that Apple has no obligation whatsoever + to furnish any maintenance and support services with respect to the App Store Sourced Application. +

                  +

                  (c) In the event of any failure of the App Store Sourced + Application to conform to any applicable warranty, you may notify Apple, and Apple will refund the purchase + price for the App Store Sourced Application to you and to the maximum extent permitted by applicable law, + Apple will have no other warranty obligation whatsoever with respect to the App Store Sourced Application. + As between PCI and Apple, any other claims, losses, liabilities, damages, costs or expenses attributable to + any failure to conform to any warranty will be the sole responsibility of PCI.

                  +

                  (d) You and PCI acknowledge that, as between PCI and Apple, + Apple is not responsible for addressing any claims you have or any claims of any third party relating to the + App Store Sourced Application or your possession and use of the App Store Sourced Application, including, + but not limited to: (i) product liability claims; (ii) any claim that the App Store Sourced Application + fails to conform to any applicable legal or regulatory requirement; and (iii) claims arising under consumer + protection or similar legislation.

                  +

                  (e) You and PCI acknowledge that, in the event of any + third-party claim that the App Store Sourced Application or your possession and use of that App Store + Sourced Application infringes that third party’s intellectual property rights, as between PCI and + Apple, PCI, not Apple, will be solely responsible for the investigation, defense, settlement and discharge + of any such intellectual property infringement claim to the extent required by this Agreement.

                  +

                  (f) You and PCI acknowledge and agree that Apple, and + Apple’s subsidiaries, are third-party beneficiaries of this Agreement as related to your license of + the App Store Sourced Application, and that, upon your acceptance of the terms and conditions of this + Agreement, Apple will have the right (and will be deemed to have accepted the right) to enforce this + Agreement as related to your license of the App Store Sourced Application against you as a third-party + beneficiary thereof.

                  +

                  (g) Without limiting any other terms of this Agreement, you + must comply with all applicable third-party terms of agreement when using the App Store Sourced + Application.

                  +

                  14.14 Consumer Complaints. + In accordance with California Civil Code §1789.3, you may report complaints to the Complaint Assistance + Unit of the Division of Consumer Services of the California Department of Consumer Affairs by contacting + them in writing at 1625 North Market Blvd., Suite N 112, Sacramento, CA 95834, or by telephone at (800) + 952-5210.

                  +

                  14.15 Entire Agreement. This + Agreement is the final, complete and exclusive agreement of the parties with respect to the subject matter + hereof and supersedes and merges all prior discussions between the parties with respect to such subject + matter.

                  diff --git a/app/locales/eula/ht_html.js b/app/locales/eula/ht_html.js index 559dac1eb7..d106f848ae 100644 --- a/app/locales/eula/ht_html.js +++ b/app/locales/eula/ht_html.js @@ -353,7 +353,7 @@ export default `

                  9. LIMITASYON RESPONSABLITE.

                  - 1.1 Limit responsabilite nou de sèten domaj. OU KONPRANN AK + 9.1 Limit responsabilite nou de sèten domaj. OU KONPRANN AK dakò KI PA GEN EVÈNMAN PATI pati PCI YON RESPONSAB POU NENPT PÈS POU PROFIT, BLIJI PÈSONÈL, DANJE PWOPRIYETE, MOVE MALADI, ENFMASYON OSWA DONE, DANI INDIRÈT, ENSIDANS, ESPESYAL, OSWA DWA, OSWA DANYE oswa domaj OSWA KOTE POU @@ -370,7 +370,7 @@ export default ` ki te koze pa yon fwis PCI pati a oswa yon fo reprezantasyon FRAUDULENT.

                  - 1.2 Kap sou Responsabilite. POU PWOJÈ maksimòm PEMISYE POU + 9.2 Kap sou Responsabilite. POU PWOJÈ maksimòm PEMISYE POU LWA, KI KOTE POU NENPAY NENPOT POU KONTW CONCH KI GENYEN LA, RESPONSABLITE NOU POU OU pou nenpòt domaj ki soti nan oswa ki gen rapò ak kondisyon sa yo pou itilize (pou nenpòt ki koz toutbon menm jan ak fòm nan AKSYON an), yo pral nan @@ -380,19 +380,19 @@ export default ` ki gen rapò ak kondisyon sa yo sèvi ak.

                  - 1.3 Done ou. EKSEPTE pou OBLIGASYON PCI a PWOTEJE DONE + 9.3 Done ou. EKSEPTE pou OBLIGASYON PCI a PWOTEJE DONE PÈSONÈL OU jan li te etabli nan règleman prive PCI a, PCI sipoze pa gen okenn responsablite pou tan an, DELETION, MIS-livrezon oswa echèk magazen nenpòt ki kontni (ki enkli istwa lokasyon ou), itilizatè kominikasyon oswa anviwònman pèsonalizasyon.

                  - 1.4 Baz negosyasyon an. Limit nan domaj ki etabli anwo yo se + 9.4 Baz negosyasyon an. Limit nan domaj ki etabli anwo yo se eleman fondamantal de baz la nan PAPA ant PCI ak ou.

                  -

                  2. KONSÈY.

                  +

                  10. KONSÈY.

                  - 2.1 Vyolasyon. Si PCI vin okouran nenpòt vyolasyon posib ke + 10.1 Vyolasyon. Si PCI vin okouran nenpòt vyolasyon posib ke ou nan Akò sa a, PCI rezève dwa pou envestige vyolasyon sa yo. Si, kòm yon rezilta nan ankèt la, psi kwè ke gen aktivite kriminèl ki te fèt, PCI rezève dwa pou refere ka a, epi pou kolabore ak, nenpòt ak tout otorite legal ki @@ -405,43 +405,43 @@ export default ` oswa lòt ofisyèl gouvènman yo, kòm psi, nan. sèl diskresyon li kwè ke li nesesè oswa apwopriye.

                  -

                  3. Regleman ak sispansyon.

                  +

                  11. Regleman ak sispansyon.

                  - 3.1 Regleman. Akò sa a kòmanse nan dat la lè ou aksepte yo + 11.1 Regleman. Akò sa a kòmanse nan dat la lè ou aksepte yo (jan sa dekri nan pre-an pi wo a) epi rete nan tout fòs ak efè pandan w ap itilize aplikasyon an, sòf si sispann pi bonè nan akò ak sa a Akò.

                  - 3.2 Itilize Anvan. Malgre sa ki endike anwo la a, ou rekonèt + 11.2 Itilize Anvan. Malgre sa ki endike anwo la a, ou rekonèt epi mwen dakò ke Akò sa a te kòmanse nan pi bonè pou rive nan (a) dat ou te itilize Aplikasyon an oswa (b) dat ou te aksepte Kontra sa a epi w ap rete nan tout fòs ou pandan wap itilize. Aplikasyon an, sòf si li te sispann pi bonè dapre Akò sa a.

                  - 3.3 Revokasyon ou. Si ou vle mete fen nan Akò sa a, ou ka fè + 11.3 Revokasyon ou. Si ou vle mete fen nan Akò sa a, ou ka fè sa pa efase aplikasyon an soti nan aparèy mobil ou.

                  - 3.4 Efè Termination. Fen nan akò sa a egzije pou ou efase + 11.4 Efè Termination. Fen nan akò sa a egzije pou ou efase aplikasyon an ak sispann tout itilize nan li. Tout dispozisyon ki nan akò sa a ki pa nati yo ta dwe siviv, va siviv mete fen, ki gen ladan san limit, dispozisyon pwopriyetè, avètisman garanti, ak limitasyon de responsablite.

                  - 4. Itilizatè entènasyonal yo. Aplikasyon an fèt sèlman pou + 12. Itilizatè entènasyonal yo. Aplikasyon an fèt sèlman pou itilize nan Etazini nan Amerik la. Psi pa fè okenn reprezantasyon ki aplikasyon an fonksyonèl nan lòt kote. Moun ki gen aksè oswa ki itilize aplikasyon an nan lòt peyi yo fè sa sou pwòp risk yo epi yo responsab pou itilize an konfòmite ak lwa lokal yo.

                  - 5. Rezolisyon dispit. Tanpri li akò arbitraj sa a nan Seksyon + 13. Rezolisyon dispit. Tanpri li akò arbitraj sa a nan Seksyon sa a ("Akò Abitraj") avèk anpil atansyon. Li mande pou itilizatè Ameriken abitraj diskisyon avèk psi epi limite fason ou ka chèche sekou nan men nou.

                  - 5.1 Aplikablite nan Abitraj Akò. Ou dakò ke nenpòt dispit, + 13.1 Aplikablite nan Abitraj Akò. Ou dakò ke nenpòt dispit, reklamasyon, oswa demann pou soulajman ki gen rapò nan nenpòt fason pou aksè ou oswa pou sèvi ak aplikasyon an oswa nan nenpòt ki aspè nan relasyon ou ak psi, yo pral rezoud pa abitraj obligatwa, olye ke nan tribinal, eksepte ke (1 @@ -454,7 +454,7 @@ export default ` efektif akò sa a oswa nenpòt vèsyon anvan akò sa a.

                  - 5.2 Règ Abitraj ak Forum. Lwa sou Abitraj Federal la gouvène + 13.2 Règ Abitraj ak Forum. Lwa sou Abitraj Federal la gouvène entèpretasyon ak ranfòsman akò sa a sou Abitraj. Pou kòmanse yon pwosedi abitraj, ou dwe voye yon lèt pou mande abitraj epi dekri konfli ou oswa reklamasyon ou oswa demann soulajman pou ajan ki anrejistre nou an, Samuel @@ -482,7 +482,7 @@ export default ` medyatè a ka antre nan nenpòt ki tribinal ki gen konpetans jiridiksyon.

                  - 5.3 Otorite nan abitrè. Medyatè a dwe gen otorite eksklizif + 13.3 Otorite nan abitrè. Medyatè a dwe gen otorite eksklizif pou (a) detèmine sijè ki abòde lan ak aplikabite nan Akò sa a Abitraj ak (b) rezoud nenpòt dispit ki gen rapò ak entèpretasyon, aplikabilite, aplikab, oswa fòmasyon sa a Arbitraj Akò ki gen ladan, men pa limite a, nenpòt ki afirmasyon @@ -500,7 +500,7 @@ export default ` genyen. Prim nan medyatè a se final ak obligatwa pou ou ak pou nou.

                  - 5.4 Egzanpsyon pou jijman jiri. OU AK PCI SE RENSEYE KÈK DWA + 13.4 Egzanpsyon pou jijman jiri. OU AK PCI SE RENSEYE KÈK DWA KONSTITISYONÈL AK STATWA POU VOJE NAN TRIBINAL AK YON JIJANS AN devan yon Jij oswa yon jiri. Oumenm ak PCI se olye pou chwazi tout diskisyon, reklamasyon, oswa demann pou soulajman yo dwe rezoud nan abitraj dapre Akò sou Abitraj sa @@ -511,7 +511,7 @@ export default ` limite.

                  - 5.5 Egzanpsyon pou Sekou Klas oswa Lòt ki pa Peye-endividyalize. + 13.5 Egzanpsyon pou Sekou Klas oswa Lòt ki pa Peye-endividyalize. Tout diskisyon, reklamasyon, ak Demann pou soulajman nan domèn AKIT ARBITRE sa a dwe abrite sou yon baz endividyèl epi yo pa sou yon klas oswa baz kolektif, SÈLMAN POU gen soulajman endividyèl, ak reklamasyon nan plis pase yon kliyan @@ -524,7 +524,7 @@ export default ` pou sekou yo dwe abitraj.

                  - 5.6 Dwa 30-Jou yo chwazi pou soti. Ou gen dwa pou ou pa + 13.6 Dwa 30-Jou yo chwazi pou soti. Ou gen dwa pou ou pa patisipe nan dispozisyon ki nan Kontra Abitraj sa a pa voye yon avi alekri sou desizyon ou a patisipe soti nan: legal@pathcheck.org, nan lespas 30 jou apre premye vin sijè a sa a Abitraj Akò. Avi ou a dwe enkli non ou ak adrès ou, @@ -535,28 +535,28 @@ export default ` tan kap vini an, avèk nou.

                  - 5.7 Divisibilite. Eksepte jan yo prevwa nan souseksyon 13.5, + 13.7 Divisibilite. Eksepte jan yo prevwa nan souseksyon 13.5, si nenpòt pati oswa pati nan akò sa a Abitraj yo te jwenn anba lalwa a yo dwe valab oswa ki pa ka ranfòse, Lè sa a, tankou yon pati espesifik oswa pati va gen ki pa gen fòs ak efè epi yo pral koupe ak rès la nan Abitraj la. Akò va kontinye nan fòs plen ak efè.

                  - 5.8 Siviv nan Akò. Akò sa a Abitraj ap siviv mete fen nan + 13.8 Siviv nan Akò. Akò sa a Abitraj ap siviv mete fen nan relasyon ou a ak psi.

                  - 5.9 Modifikasyon. Malgre nenpòt dispozisyon ki nan Kontra + 13.9 Modifikasyon. Malgre nenpòt dispozisyon ki nan Kontra sa-a nan kontrè a, nou dakò ke si PCI fè nenpòt ki chanjman materyèl nan lavni sa a Abitraj Akò, ou ka rejte ki chanjman nan trant (30) jou de chanjman sa yo vin efektif pa ekri PCI nan adrès sa a: Path Check, Inc. PO Box 441621, Somerville, MA 02144

                  - 6. DISPOZISYON JENERAL YO
                  + 14. DISPOZISYON JENERAL YO

                  - 6.1 Kominikasyon elektwonik. Kominikasyon ant ou menm ak PCI + 14.1 Kominikasyon elektwonik. Kominikasyon ant ou menm ak PCI ka pran plas atravè mwayen elektwonik, si ou voye yon imèl PCI, oswa si PCI poste avi nan Aplikasyon an oswa atravè mizajou yo te fè nan Aplikasyon an annakò avèk sa a Regleman pou Itilize oswa kominike avèk ou via- lapòs. Pou @@ -568,7 +568,7 @@ export default ` Komès Nasyonal nan 15 US.C. §7001 et seq. ("E-Siyen").

                  - 6.2 Lansman. Ou fin divilge Pèmi yo PCI ak siksesè yo soti + 14.2 Lansman. Ou fin divilge Pèmi yo PCI ak siksesè yo soti nan reklamasyon, demand, nenpòt ak tout pèt, domaj, dwa, ak aksyon nenpòt kalite, ki gen ladan blesi pèsonèl, lanmò, ak domaj pwopriyete, ki se swa dirèkteman oswa endirèkteman ki gen rapò ak oswa rive soti nan nenpòt ki @@ -588,61 +588,61 @@ export default ` Aplikasyon an bay la anba a.

                  - 6.3 Plasman. Akò sa a, ak dwa ou yo ak obligasyon ki anba la + 14.3 Plasman. Akò sa a, ak dwa ou yo ak obligasyon ki anba la a, pa ka asiyen, soutretan, delege oswa otreman transfere pa ou san ou pa konsantman PCI anvan ekri, ak nenpòt tantativ tantasyon, tretman, delegasyon, oswa transfè an vyolasyon sa ki ekri pi wo a pral nil epi yo anile. .

                  - 6.4 Fòs majè. PCI pa dwe responsab pou okenn reta oswa echèk + 14.4 Fòs majè. PCI pa dwe responsab pou okenn reta oswa echèk nan fè rezilta ki soti nan kòz deyò kontwòl rezonab li yo, ki gen ladan, men pa limite a, zak Bondye a, lagè, teworis, revòlt, embargos, zak otorite sivil oswa militè, dife, inondasyon ,. aksidan, grèv oswa mank fasilite transpò, gaz, enèji, travay oswa materyèl.

                  - 6.5 Kesyon, Plent, Reklamasyon. Si ou gen nenpòt kesyon, + 14.5 Kesyon, Plent, Reklamasyon. Si ou gen nenpòt kesyon, plent oswa reklamasyon ki gen rapò ak Aplikasyon an, tanpri kontakte nou nan: support@pathcheck.org.

                  - 6.6 Eksklizif Venue. Nan mezi pati yo gen pèmisyon dapre Akò + 14.6 Eksklizif Venue. Nan mezi pati yo gen pèmisyon dapre Akò sa a pou kòmanse yon litij nan yon tribinal, tou de ou menm ak PCI dakò ke tout reklamasyon ak diskisyon ki rive soti nan oswa ki gen rapò ak sa a Akò pral plede sèlman nan tribinal yo leta oswa federal ki sitiye nan Boston, Massachusetts.

                  - 6.7 Lwa sou Gouvènman TÈM YO AK NENPT AKSYON KI KI TE RELIJE + 14.7 Lwa sou Gouvènman TÈM YO AK NENPT AKSYON KI KI TE RELIJE A ANREJE AK INTERPRETE PA AK LWA NAN LWA COMMONWEALTH OF MASSACHUSETTS, ki konsistan avèk Lwa sou Arbitraj FEDERAL, san bay efè sou nenpòt prensip ki bay APLIKASYON LWA POU YON LDT JURISDIKSYON. KONVANSYON Nasyonzini an sou KONTRA POU VANN entènasyonal la nan machandiz pa aplike pou akò sa a.

                  - 6.8 Chwa langaj. Li se vle eksprime pati yo ke Akò sa a ak + 14.8 Chwa langaj. Li se vle eksprime pati yo ke Akò sa a ak tout dokiman ki gen rapò ak yo te trase moute nan lang angle.

                  - 6.9 Avi. Ou ka remèt yon avi bay PCI nan adrès sa a: Path + 14.9 Avi. Ou ka remèt yon avi bay PCI nan adrès sa a: Path Check, Inc. PO Box 441621, Somerville MA 02144. Yo dwe konsidere yon avi konsa lè yo resevwa pa PCI pa yon lèt bay pa sèvis livrezon lannwit ke yo rekonèt nasyonalman oswa lapòs premye pòs klas pre-peye nan nan adrès ki pi wo a.

                  - 6.10 Renonsyasyon. Nenpòt egzanpsyon oswa echèk nan ranfòse + 14.10 Renonsyasyon. Nenpòt egzanpsyon oswa echèk nan ranfòse nenpòt dispozisyon nan kontra sa-a nan yon sèl okazyon pa pral jije yon egzansyon nan nenpòt ki lòt pwovizyon oswa nan dispozisyon sa a sou nenpòt ki lòt okazyon.

                  - 6.11 Divisibilite. Si nenpòt pòsyon nan Kontra sa-a se fèt + 14.11 Divisibilite. Si nenpòt pòsyon nan Kontra sa-a se fèt valab oswa ki pa aplikab, yo dwe entèprete pòsyon sa a nan yon fason yo reflete, kòm prèske ke posib, entansyon orijinal la nan pati yo, ak pòsyon ki rete yo ap rete nan tout fòs ak efè.

                  - 6.12 Ekspòtasyon kontwòl. Ou pa ka itilize, ekspòtasyon, + 14.12 Ekspòtasyon kontwòl. Ou pa ka itilize, ekspòtasyon, enpòte, oswa transfere Aplikasyon an eksepte jan otorize pa lalwa Etazini, lwa yo nan jiridiksyon an nan ki ou te jwenn Pwopriyete PCI, ak nenpòt lòt lwa ki aplikab yo. An patikilye, men san limit, Aplikasyon an pa ka ekspòte oswa @@ -663,7 +663,7 @@ export default ` règleman sa yo.

                  - 6.13 Jwenn aksè ak telechaje aplikasyon an soti nan iTunes. + 14.13 Jwenn aksè ak telechaje aplikasyon an soti nan iTunes. Sa ki annapre yo aplike a nenpòt ki App Store Sourcing Aplikasyon jwenn aksè a oswa telechaje soti nan Apple App Store:

                  @@ -752,14 +752,14 @@ export default `

                - 6.14 Plent Konsomatè. An akò avèk Kòd Sivil Kalifòni §1789.3, + 14.14 Plent Konsomatè. An akò avèk Kòd Sivil Kalifòni §1789.3, ou ka rapòte plent bay Inite a Asistans Plent nan Divizyon Sèvis pou Konsomatè nan Depatman Kalifòni nan Zafè Konsomatè nan Kalifòni lè w kontakte yo alekri nan 1625 North Market Blvd., Suite N 112, Sacramento, CA 95834, oswa pa telefòn nan (800) 952-5210.

                - 6.15 Tout Akò. Akò sa a se akò final, konplè ak eksklizif de + 14.15 Tout Akò. Akò sa a se akò final, konplè ak eksklizif de pati yo ki gen rapò ak matyè a nan sa a ak ranplase ak melanje tout diskisyon anvan ant pati yo ki gen rapò ak matyè sa yo.

                From a0aaeaf65da5dff4b968274e633e27467565981e Mon Sep 17 00:00:00 2001 From: efalkner Date: Sun, 26 Apr 2020 17:07:43 -0400 Subject: [PATCH 28/60] Feature/creole tests (#690) * enable haitian creole tests * get the tests running with onboarding 5 changes * revert accidental commit for config * remove check for notification as it's not supported * upload screenshots on failure * let's save some failed screenshots * try disabling builds if the only code pushed is for e2e * disable the check for content on the home screen as it's not always returning the same thing * comment out the paths filter because of our requirements for PR merge * remove the paths filter because of our requirements for PR merge --- .github/workflows/test.yml | 2 + app/helpers/Intersect.js | 9 ++- app/views/onboarding/Onboarding5.js | 5 +- e2e/InsufficientLocationPermissions.spec.js | 19 ------ e2e/NoLocationPermissions.spec.js | 19 ------ e2e/NoNotificationsPermissions.spec.js | 19 ------ e2e/Onboarding.spec.js | 43 -------------- e2e/Permissions.spec.js | 19 ------ e2e/UnsignedEula.spec.js | 19 ------ e2e/config.json | 6 -- e2e/helpers/language.js | 13 ++++ e2e/helpers/onboarding.js | 26 ++++---- .../InsufficientLocationPermissions.spec.js | 37 ++++++++++++ e2e/onboarding/NoLocationPermissions.spec.js | 36 +++++++++++ .../NoNotificationsPermissions.spec.js | 37 ++++++++++++ e2e/onboarding/Onboarding.spec.js | 59 +++++++++++++++++++ e2e/onboarding/Permissions.spec.js | 36 +++++++++++ e2e/onboarding/UnsignedEula.spec.js | 36 +++++++++++ e2e/pages/EnableLocation.po.js | 5 +- e2e/pages/FinishSetup.po.js | 12 ++-- e2e/pages/Home.po.js | 34 +++++++++++ e2e/pages/Onboarding1.po.js | 12 ++-- e2e/pages/Onboarding2.po.js | 13 ++-- e2e/pages/Onboarding3.po.js | 13 ++-- e2e/pages/Onboarding4.po.js | 13 ++-- e2e/pages/Onboarding5.po.js | 27 ++++++--- e2e/pages/Onboarding6.po.js | 23 -------- e2e/pages/SignEula.po.js | 10 ++-- jest/config.js | 6 ++ jest/detox.config.js | 10 ++++ jestSetupFile.js => jest/setupFile.js | 0 package.json | 22 ++++--- 32 files changed, 394 insertions(+), 246 deletions(-) delete mode 100644 e2e/InsufficientLocationPermissions.spec.js delete mode 100644 e2e/NoLocationPermissions.spec.js delete mode 100644 e2e/NoNotificationsPermissions.spec.js delete mode 100644 e2e/Onboarding.spec.js delete mode 100644 e2e/Permissions.spec.js delete mode 100644 e2e/UnsignedEula.spec.js delete mode 100644 e2e/config.json create mode 100644 e2e/helpers/language.js create mode 100644 e2e/onboarding/InsufficientLocationPermissions.spec.js create mode 100644 e2e/onboarding/NoLocationPermissions.spec.js create mode 100644 e2e/onboarding/NoNotificationsPermissions.spec.js create mode 100644 e2e/onboarding/Onboarding.spec.js create mode 100644 e2e/onboarding/Permissions.spec.js create mode 100644 e2e/onboarding/UnsignedEula.spec.js create mode 100644 e2e/pages/Home.po.js delete mode 100644 e2e/pages/Onboarding6.po.js create mode 100644 jest/config.js create mode 100644 jest/detox.config.js rename jestSetupFile.js => jest/setupFile.js (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad921a2c83..eae889ff41 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -132,6 +132,7 @@ jobs: - run: yarn test:e2e:iphone11 - name: Upload Screenshots + if: always() uses: actions/upload-artifact@v1 with: name: iPhone 11 screenshots @@ -182,6 +183,7 @@ jobs: - run: yarn test:e2e:iphone-se - name: Upload Screenshots + if: always() uses: actions/upload-artifact@v1 with: name: iPhone SE screenshots diff --git a/app/helpers/Intersect.js b/app/helpers/Intersect.js index fe0829a73d..196f7636a2 100644 --- a/app/helpers/Intersect.js +++ b/app/helpers/Intersect.js @@ -275,10 +275,13 @@ export function checkIntersect() { * Returns the array of day bins (mostly for debugging purposes) */ async function asyncCheckIntersect() { - // first things first ... is it time to actually try the intersection? - let last_checked_ms =Number(await GetStoreData(LAST_CHECKED)); - if (last_checked_ms + MIN_CHECK_INTERSECT_INTERVAL * 60 * 1000 > dayjs().valueOf()) return null; + let last_checked_ms = Number(await GetStoreData(LAST_CHECKED)); + if ( + last_checked_ms + MIN_CHECK_INTERSECT_INTERVAL * 60 * 1000 > + dayjs().valueOf() + ) + return null; // Set up the empty set of dayBins for intersections, and the array for the news urls let dayBins = getEmptyLocationBins(); diff --git a/app/views/onboarding/Onboarding5.js b/app/views/onboarding/Onboarding5.js index b4ef241d68..4ae54570bb 100644 --- a/app/views/onboarding/Onboarding5.js +++ b/app/views/onboarding/Onboarding5.js @@ -193,10 +193,7 @@ class Onboarding extends Component { getTitleTextView() { if (!this.isLocationChecked() || !this.isNotificationChecked()) { return ( - + {this.getTitleText()} ); diff --git a/e2e/InsufficientLocationPermissions.spec.js b/e2e/InsufficientLocationPermissions.spec.js deleted file mode 100644 index 4c4becd65b..0000000000 --- a/e2e/InsufficientLocationPermissions.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import { navigateThroughOnboarding } from './helpers/onboarding'; -import FinishSetup from './pages/FinishSetup.po.js'; - -describe('Location set to `inuse` and notifications `true` set', () => { - beforeAll(async () => { - await device.launchApp({ - permissions: { location: 'inuse', notifications: 'YES' }, - newInstance: true, - }); - await navigateThroughOnboarding(); - }); - - it('Displays an error page about missing location permissions', async () => { - await FinishSetup.isOnScreen(); - await FinishSetup.takeScreenshot(); - await FinishSetup.tapButton(); - await device.takeScreenshot('No Location Permissions'); - }); -}); diff --git a/e2e/NoLocationPermissions.spec.js b/e2e/NoLocationPermissions.spec.js deleted file mode 100644 index cf020f6a66..0000000000 --- a/e2e/NoLocationPermissions.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import { navigateThroughOnboarding } from './helpers/onboarding'; -import FinishSetup from './pages/FinishSetup.po.js'; - -describe('Location set to `never` and notifications `true` set', () => { - beforeAll(async () => { - await device.launchApp({ - permissions: { location: 'never', notifications: 'YES' }, - newInstance: true, - }); - await navigateThroughOnboarding(); - }); - - it('Displays an error page about missing location permissions', async () => { - await FinishSetup.isOnScreen(); - await FinishSetup.takeScreenshot(); - await FinishSetup.tapButton(); - await device.takeScreenshot('No Location Permissions'); - }); -}); diff --git a/e2e/NoNotificationsPermissions.spec.js b/e2e/NoNotificationsPermissions.spec.js deleted file mode 100644 index b9a3240f3e..0000000000 --- a/e2e/NoNotificationsPermissions.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import { navigateThroughOnboarding } from './helpers/onboarding'; -import FinishSetup from './pages/FinishSetup.po.js'; - -describe('Location set to `always` and notifications `false` set', () => { - beforeAll(async () => { - await device.launchApp({ - permissions: { location: 'always', notifications: 'NO' }, - newInstance: true, - }); - await navigateThroughOnboarding(); - }); - - it('Allows the user to proceed', async () => { - await FinishSetup.isOnScreen(); - await FinishSetup.takeScreenshot(); - await FinishSetup.tapButton(); - await device.takeScreenshot('No Notification Permissions'); - }); -}); diff --git a/e2e/Onboarding.spec.js b/e2e/Onboarding.spec.js deleted file mode 100644 index ea285574fd..0000000000 --- a/e2e/Onboarding.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import EnableLocation from './pages/EnableLocation.po.js'; -import Onboarding1 from './pages/Onboarding1.po.js'; -import Onboarding2 from './pages/Onboarding2.po.js'; -import Onboarding3 from './pages/Onboarding3.po.js'; -import Onboarding4 from './pages/Onboarding4.po.js'; -import Onboarding5 from './pages/Onboarding5.po.js'; -import SignEula from './pages/SignEula.po.js'; - -describe('Onboarding visual appearance', () => { - it('Navigates through the onboarding without visual regression', async () => { - await device.launchApp({ - newInstance: true, - }); - - await Onboarding1.isOnScreen(); - await Onboarding1.takeScreenshot(); - await Onboarding1.tapButton(); - - await SignEula.sign(); - await SignEula.takeScreenshot(); - await SignEula.tapButton(); - - await Onboarding2.isOnScreen(); - await Onboarding2.takeScreenshot(); - await Onboarding2.tapButton(); - - await Onboarding3.isOnScreen(); - await Onboarding3.takeScreenshot(); - await Onboarding3.tapButton(); - - await Onboarding4.isOnScreen(); - await Onboarding4.takeScreenshot(); - await Onboarding4.tapButton(); - - await Onboarding5.isOnScreen(); - await Onboarding5.takeScreenshot(); - await Onboarding5.tapButton(); - - await EnableLocation.takeScreenshot(); - await EnableLocation.tapButton(); - await EnableLocation.takeMenuScreenshot(); - }); -}); diff --git a/e2e/Permissions.spec.js b/e2e/Permissions.spec.js deleted file mode 100644 index ed6ff85edf..0000000000 --- a/e2e/Permissions.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import { navigateThroughOnboarding } from './helpers/onboarding'; -import FinishSetup from './pages/FinishSetup.po.js'; - -describe('Permissions: Location `always` and notifications `true` are chosen', () => { - beforeAll(async () => { - await device.launchApp({ - permissions: { location: 'always', notifications: 'YES' }, - newInstance: true, - }); - await navigateThroughOnboarding(); - }); - - it('Successfully completes device setup', async () => { - await FinishSetup.isOnScreen(); - await FinishSetup.takeScreenshot(); - await FinishSetup.tapButton(); - await device.takeScreenshot('Post Setup'); - }); -}); diff --git a/e2e/UnsignedEula.spec.js b/e2e/UnsignedEula.spec.js deleted file mode 100644 index 3b56ac9bb6..0000000000 --- a/e2e/UnsignedEula.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import Onboarding1 from './pages/Onboarding1.po.js'; -import SignEula from './pages/SignEula.po.js'; - -describe('Cannot continue without signing the EULA', () => { - beforeAll(async () => { - await device.launchApp({ - newInstance: true, - }); - }); - - it('Does not allow the user to proceed', async () => { - await Onboarding1.isOnScreen(); - await Onboarding1.tapButton(); - - await SignEula.tapButton(); - await SignEula.takeScreenshot(); - await device.takeScreenshot('Unsigned Eula Continue Attempt'); - }); -}); diff --git a/e2e/config.json b/e2e/config.json deleted file mode 100644 index 3ff37ef8ec..0000000000 --- a/e2e/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "setupFilesAfterEnv": ["./init.js"], - "testEnvironment": "node", - "reporters": ["detox/runners/jest/streamlineReporter"], - "verbose": true -} diff --git a/e2e/helpers/language.js b/e2e/helpers/language.js new file mode 100644 index 0000000000..7c7019579f --- /dev/null +++ b/e2e/helpers/language.js @@ -0,0 +1,13 @@ +import * as english from '../../app/locales/en.json'; +import * as haitian from '../../app/locales/ht.json'; + +export const getLanguageStrings = localeName => { + switch (localeName) { + case 'en-US': + return english; + case 'ht-HT': + return haitian; + default: + return english; + } +}; diff --git a/e2e/helpers/onboarding.js b/e2e/helpers/onboarding.js index 62fcbcb559..89ace6d6c9 100644 --- a/e2e/helpers/onboarding.js +++ b/e2e/helpers/onboarding.js @@ -5,22 +5,22 @@ import Onboarding4 from '../pages/Onboarding4.po.js'; import Onboarding5 from '../pages/Onboarding5.po.js'; import SignEula from '../pages/SignEula.po.js'; -export const navigateThroughOnboarding = async () => { - await Onboarding1.isOnScreen(); - await Onboarding1.tapButton(); +export const navigateThroughOnboarding = async languageStrings => { + await Onboarding1.isOnScreen(languageStrings); + await Onboarding1.tapButton(languageStrings); - await SignEula.sign(); - await SignEula.tapButton(); + await SignEula.sign(languageStrings); + await SignEula.tapButton(languageStrings); - await Onboarding2.isOnScreen(); - await Onboarding2.tapButton(); + await Onboarding2.isOnScreen(languageStrings); + await Onboarding2.tapButton(languageStrings); - await Onboarding3.isOnScreen(); - await Onboarding3.tapButton(); + await Onboarding3.isOnScreen(languageStrings); + await Onboarding3.tapButton(languageStrings); - await Onboarding4.isOnScreen(); - await Onboarding4.tapButton(); + await Onboarding4.isOnScreen(languageStrings); + await Onboarding4.tapButton(languageStrings); - await Onboarding5.isOnScreen(); - await Onboarding5.tapButton(); + await Onboarding5.isOnScreen(languageStrings); + await Onboarding5.finishSetup(languageStrings); }; diff --git a/e2e/onboarding/InsufficientLocationPermissions.spec.js b/e2e/onboarding/InsufficientLocationPermissions.spec.js new file mode 100644 index 0000000000..5251022fff --- /dev/null +++ b/e2e/onboarding/InsufficientLocationPermissions.spec.js @@ -0,0 +1,37 @@ +import { getLanguageStrings } from '../helpers/language'; +import { navigateThroughOnboarding } from '../helpers/onboarding'; +import Home from '../pages/Home.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`Insufficient Permissions test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'inuse', notifications: 'YES' }, + }); + languageStrings = getLanguageStrings(locale); + }); + + describe('Location set to `inuse` and notifications `true` set', () => { + beforeAll(async () => { + await navigateThroughOnboarding(languageStrings); + }); + + it('Allows the user to go to the Home page but shows a notification that location is required', async () => { + // await Home.hasLocationDisabled(languageStrings); + await Home.takeScreenshot(); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); + }); +}); diff --git a/e2e/onboarding/NoLocationPermissions.spec.js b/e2e/onboarding/NoLocationPermissions.spec.js new file mode 100644 index 0000000000..ca05225400 --- /dev/null +++ b/e2e/onboarding/NoLocationPermissions.spec.js @@ -0,0 +1,36 @@ +import { getLanguageStrings } from '../helpers/language'; +import { navigateThroughOnboarding } from '../helpers/onboarding'; +import Home from '../pages/Home.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`No Location Permissions test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'never', notifications: 'YES' }, + }); + languageStrings = getLanguageStrings(locale); + }); + describe('Location set to `never` and notifications `true` set', () => { + beforeAll(async () => { + await navigateThroughOnboarding(languageStrings); + }); + + it('Allows the user to go to the Home page but shows a notification that location is required', async () => { + // await Home.hasLocationDisabled(languageStrings); + await Home.takeScreenshot(); + }); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); +}); diff --git a/e2e/onboarding/NoNotificationsPermissions.spec.js b/e2e/onboarding/NoNotificationsPermissions.spec.js new file mode 100644 index 0000000000..02af75d919 --- /dev/null +++ b/e2e/onboarding/NoNotificationsPermissions.spec.js @@ -0,0 +1,37 @@ +import { getLanguageStrings } from '../helpers/language'; +import { navigateThroughOnboarding } from '../helpers/onboarding'; +import Home from '../pages/Home.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`No Notifications test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'always', notifications: 'NO' }, + }); + languageStrings = getLanguageStrings(locale); + }); + + describe('Location set to `always` and notifications `false` set', () => { + beforeAll(async () => { + await navigateThroughOnboarding(languageStrings); + }); + + it('Allows the user to go to the Home page and does not display a notification', async () => { + await Home.hasNoKnownContact(languageStrings); + await Home.takeScreenshot(); + }); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); +}); diff --git a/e2e/onboarding/Onboarding.spec.js b/e2e/onboarding/Onboarding.spec.js new file mode 100644 index 0000000000..89cd932ed9 --- /dev/null +++ b/e2e/onboarding/Onboarding.spec.js @@ -0,0 +1,59 @@ +import { getLanguageStrings } from '../helpers/language'; +import Onboarding1 from '../pages/Onboarding1.po.js'; +import Onboarding2 from '../pages/Onboarding2.po.js'; +import Onboarding3 from '../pages/Onboarding3.po.js'; +import Onboarding4 from '../pages/Onboarding4.po.js'; +import Onboarding5 from '../pages/Onboarding5.po.js'; +import SignEula from '../pages/SignEula.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`Onboarding test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + }); + languageStrings = getLanguageStrings(locale); + }); + + describe('Onboarding visual appearance', () => { + it('Navigates through the onboarding without visual regression', async () => { + await Onboarding1.isOnScreen(languageStrings); + await Onboarding1.takeScreenshot(); + await Onboarding1.tapButton(languageStrings); + + await SignEula.sign(languageStrings); + await SignEula.takeScreenshot(); + await SignEula.tapButton(languageStrings); + + await Onboarding2.isOnScreen(languageStrings); + await Onboarding2.takeScreenshot(); + await Onboarding2.tapButton(languageStrings); + + await Onboarding3.isOnScreen(languageStrings); + await Onboarding3.takeScreenshot(); + await Onboarding3.tapButton(languageStrings); + + await Onboarding4.isOnScreen(languageStrings); + await Onboarding4.takeScreenshot(); + await Onboarding4.tapButton(languageStrings); + + await Onboarding5.isOnScreen(languageStrings); + await Onboarding5.takeScreenshot(); + + await Onboarding5.enableLocation(languageStrings); + await Onboarding5.takeMenuScreenshot(); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); + }); +}); diff --git a/e2e/onboarding/Permissions.spec.js b/e2e/onboarding/Permissions.spec.js new file mode 100644 index 0000000000..81789eefb7 --- /dev/null +++ b/e2e/onboarding/Permissions.spec.js @@ -0,0 +1,36 @@ +import { getLanguageStrings } from '../helpers/language'; +import { navigateThroughOnboarding } from '../helpers/onboarding'; +import Home from '../pages/Home.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`Permissions test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'always', notifications: 'YES' }, + }); + languageStrings = getLanguageStrings(locale); + }); + describe('Permissions: Location `always` and notifications `true` are chosen', () => { + beforeAll(async () => { + await navigateThroughOnboarding(languageStrings); + }); + + it('Successfully completes device setup', async () => { + await Home.hasNoKnownContact(languageStrings); + await Home.takeScreenshot(); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); + }); +}); diff --git a/e2e/onboarding/UnsignedEula.spec.js b/e2e/onboarding/UnsignedEula.spec.js new file mode 100644 index 0000000000..94710d9c7d --- /dev/null +++ b/e2e/onboarding/UnsignedEula.spec.js @@ -0,0 +1,36 @@ +import { getLanguageStrings } from '../helpers/language'; +import Onboarding1 from '../pages/Onboarding1.po.js'; +import SignEula from '../pages/SignEula.po.js'; + +let languageStrings = {}; + +['en-US', 'ht-HT'].forEach(locale => { + describe(`No Notifications test suite in ${locale}`, () => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'always', notifications: 'YES' }, + }); + languageStrings = getLanguageStrings(locale); + }); + + describe('Cannot continue without signing the EULA', () => { + it('Does not allow the user to proceed', async () => { + await Onboarding1.isOnScreen(languageStrings); + await Onboarding1.tapButton(languageStrings); + + await SignEula.tapButton(languageStrings); + await device.takeScreenshot('Unsigned Eula Continue Attempt'); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); + }); + }); + }); +}); diff --git a/e2e/pages/EnableLocation.po.js b/e2e/pages/EnableLocation.po.js index fa4e0e32a0..56b5afc054 100644 --- a/e2e/pages/EnableLocation.po.js +++ b/e2e/pages/EnableLocation.po.js @@ -1,10 +1,9 @@ -const buttonlabel = 'Enable Location'; const screenshotText = 'Enable Location Page'; const screenShotWithMenuText = 'Location Permissions Dialog'; class EnableLocation { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_enable_location)).tap(); } async takeScreenshot() { diff --git a/e2e/pages/FinishSetup.po.js b/e2e/pages/FinishSetup.po.js index 4711fcfbea..f5bed5d3b8 100644 --- a/e2e/pages/FinishSetup.po.js +++ b/e2e/pages/FinishSetup.po.js @@ -1,20 +1,20 @@ /* eslint-disable */ -const buttonlabel = 'Finish Setup'; -const screenText = 'All finished'; const screenshotText = 'Finish Setup Page'; class EnableLocation { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_finish_set_up)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { + async isOnScreen(languageStrings) { // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.text(screenText))).toBeVisible(); + await expect( + element(by.label(languageStrings.label.launch_done_header)), + ).toBeVisible(); } } diff --git a/e2e/pages/Home.po.js b/e2e/pages/Home.po.js new file mode 100644 index 0000000000..8808bb46f8 --- /dev/null +++ b/e2e/pages/Home.po.js @@ -0,0 +1,34 @@ +/* eslint-disable */ +const screenshotText = 'Home'; + +class Home { + async takeScreenshot() { + await device.takeScreenshot(screenshotText); + } + + async hasNoKnownContact(languageStrings) { + await expect( + element(by.label(languageStrings.label.home_no_contact_header)), + ).toBeVisible(); + } + + async hasLocationDisabled(languageStrings) { + await expect( + element(by.label(languageStrings.label.home_unknown_header)), + ).toBeVisible(); + await expect( + element(by.label(languageStrings.label.home_unknown_subtext)), + ).toBeVisible(); + } + + async hasLocationHistoryDisabled(languageStrings) { + await expect( + element(by.label(languageStrings.label.home_setting_off_header)), + ).toBeVisible(); + await expect( + element(by.label(languageStrings.label.home_setting_off_subtext)), + ).toBeVisible(); + } +} + +export default new Home(); diff --git a/e2e/pages/Onboarding1.po.js b/e2e/pages/Onboarding1.po.js index f8dcc3cf77..645e60f424 100644 --- a/e2e/pages/Onboarding1.po.js +++ b/e2e/pages/Onboarding1.po.js @@ -1,19 +1,19 @@ /* eslint-disable */ -const buttonlabel = 'Get Started'; -const screenText = 'The way back to normal starts here.'; const screenshotText = 'Onboarding - Page 1'; class Onboarding1 { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_get_started)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { - await expect(element(by.text(screenText))).toBeVisible(); + async isOnScreen(languageStrings) { + await expect( + element(by.label(languageStrings.label.launch_screen1_header)), + ).toBeVisible(); } } diff --git a/e2e/pages/Onboarding2.po.js b/e2e/pages/Onboarding2.po.js index 6e63ffd59d..51539fa1c4 100644 --- a/e2e/pages/Onboarding2.po.js +++ b/e2e/pages/Onboarding2.po.js @@ -1,21 +1,20 @@ /* eslint-disable */ -const buttonlabel = 'Next'; -const screenText = - 'Get notified if you cross paths with someone later diagnosed for COVID-19.'; const screenshotText = 'Onboarding - Page 2'; class Onboarding2 { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_next)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { + async isOnScreen(languageStrings) { // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.text(screenText))).toBeVisible(); + await expect( + element(by.label(languageStrings.label.launch_screen2_header)), + ).toBeVisible(); } } diff --git a/e2e/pages/Onboarding3.po.js b/e2e/pages/Onboarding3.po.js index 1bea5b157d..c4c1c6e607 100644 --- a/e2e/pages/Onboarding3.po.js +++ b/e2e/pages/Onboarding3.po.js @@ -1,21 +1,20 @@ /* eslint-disable */ -const buttonlabel = 'Next'; -const screenText = - 'If you test positive, you can choose to donate your data anonymously'; const screenshotText = 'Onboarding - Page 3'; class Onboarding3 { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_next)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { + async isOnScreen(languageStrings) { // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.text(screenText))).toBeVisible(); + await expect( + element(by.label(languageStrings.label.launch_screen3_header)), + ).toBeVisible(); } } diff --git a/e2e/pages/Onboarding4.po.js b/e2e/pages/Onboarding4.po.js index 15767e87cb..2bb27b8a81 100644 --- a/e2e/pages/Onboarding4.po.js +++ b/e2e/pages/Onboarding4.po.js @@ -1,21 +1,20 @@ /* eslint-disable */ -const buttonlabel = 'Set up my phone'; -const screenText = - "You're in complete control. Data is only saved on your phone."; const screenshotText = 'Onboarding - Page 4'; class Onboarding4 { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_set_up_phone)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { + async isOnScreen(languageStrings) { // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.text(screenText))).toBeVisible(); + await expect( + element(by.text(languageStrings.label.launch_screen4_header)), + ).toBeVisible(); } } diff --git a/e2e/pages/Onboarding5.po.js b/e2e/pages/Onboarding5.po.js index 7576b6ae42..7ecd377369 100644 --- a/e2e/pages/Onboarding5.po.js +++ b/e2e/pages/Onboarding5.po.js @@ -1,22 +1,33 @@ /* eslint-disable */ -const buttonLabel = 'Next'; -const screenTitleTestId = 'Header'; -const screenSubtitle = 'Subheader'; const screenshotText = 'Onboarding - Page 5'; +const screenShotWithMenuText = 'Location Permissions Dialog'; class Onboarding5 { - async tapButton() { - await element(by.label(buttonLabel)).tap(); + async finishSetup(languageStrings) { + await element(by.label(languageStrings.label.launch_finish_set_up)).tap(); + } + + async enableLocation(languageStrings) { + await element(by.label(languageStrings.label.launch_enable_location)).tap(); + } + + async enableNotification(languageStrings) { + await element(by.label(languageStrings.label.launch_enable_notif)).tap(); } async takeScreenshot() { await device.takeScreenshot(screenshotText); } - async isOnScreen() { + async takeMenuScreenshot() { + await device.takeScreenshot(screenShotWithMenuText); + } + + async isOnScreen(languageStrings) { // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.id(screenTitleTestId))).toBeVisible(); - await expect(element(by.id(screenSubtitle))).toBeVisible(); + await expect( + element(by.label(languageStrings.label.launch_location_access)), + ).toBeVisible(); } } diff --git a/e2e/pages/Onboarding6.po.js b/e2e/pages/Onboarding6.po.js deleted file mode 100644 index 7458ea9847..0000000000 --- a/e2e/pages/Onboarding6.po.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -const buttonLabel = 'Next'; -const screenTitleTestId = 'Header'; -const screenSubtitle = 'Subheader'; -const screenshotText = 'Onboarding - Page 6'; - -class Onboarding6 { - async tapButton() { - await element(by.label(buttonLabel)).tap(); - } - - async takeScreenshot() { - await device.takeScreenshot(screenshotText); - } - - async isOnScreen() { - // eslint-disable-next-line jest/no-standalone-expect - await expect(element(by.id(screenTitleTestId))).toBeVisible(); - await expect(element(by.id(screenSubtitle))).toBeVisible(); - } -} - -export default new Onboarding6(); diff --git a/e2e/pages/SignEula.po.js b/e2e/pages/SignEula.po.js index a6bfad4df1..f2063aed9b 100644 --- a/e2e/pages/SignEula.po.js +++ b/e2e/pages/SignEula.po.js @@ -1,15 +1,13 @@ /* eslint-disable */ -const buttonlabel = 'Continue'; -const signCheckboxLabel = 'I accept the licensing agreement'; const screenshotText = 'EULA Accept Page'; class SignEula { - async tapButton() { - await element(by.label(buttonlabel)).tap(); + async tapButton(languageStrings) { + await element(by.label(languageStrings.onboarding.eula_continue)).tap(); } - async sign() { - await element(by.text(signCheckboxLabel)).tap(); + async sign(languageStrings) { + await element(by.text(languageStrings.onboarding.eula_checkbox)).tap(); } async takeScreenshot() { diff --git a/jest/config.js b/jest/config.js new file mode 100644 index 0000000000..f9e9194338 --- /dev/null +++ b/jest/config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'react-native', + setupFiles: ['./jest/setupFile.js'], + rootDir: '../', + testPathIgnorePatterns: ['e2e', 'node_modules'], +}; diff --git a/jest/detox.config.js b/jest/detox.config.js new file mode 100644 index 0000000000..282f3f146e --- /dev/null +++ b/jest/detox.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: 'react-native', + setupFiles: ['./jest/setupFile.js'], + setupFilesAfterEnv: ['./e2e/init.js'], + testEnvironment: 'node', + reporters: ['detox/runners/jest/streamlineReporter'], + verbose: true, + rootDir: '../', + testPathIgnorePatterns: ['node_modules', 'app'], +}; diff --git a/jestSetupFile.js b/jest/setupFile.js similarity index 100% rename from jestSetupFile.js rename to jest/setupFile.js diff --git a/package.json b/package.json index 63e3c37ff5..1c61c52bc6 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "test:e2e:iphone-se": "detox test -c iphone-se.sim --loglevel=warn", "test:e2e:iphone8": "detox test -c iphone8.sim --loglevel=warn", "i18n:extract": "i18next", - "test": "jest test", - "update-snapshots": "jest --updateSnapshot", + "test": "jest --config=./jest/config.js", + "update-snapshots": "jest --config=./jest/config.js --updateSnapshot", "test:dev_setup": "bats __tests__/dev_setup.test.bats" }, "lint-staged": { @@ -123,15 +123,18 @@ "style": "module" } }, - "jest": { - "preset": "react-native", - "setupFiles": [ - "./jestSetupFile.js" - ] - }, "detox": { "artifacts": { - "rootDir": "./e2e/artifacts/" + "rootDir": "./e2e/artifacts/", + "plugins": { + "screenshot": { + "shouldTakeAutomaticSnapshots": true, + "takeWhen": { + "testStart": false, + "testDone": false + } + } + } }, "configurations": { "iphone11.sim": { @@ -159,6 +162,7 @@ } } }, + "runner-config": "./jest/detox.config.js", "test-runner": "jest" } } From 5bad545e90393fd7627fddfdb74a853a93cc50af Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Sun, 26 Apr 2020 15:17:13 -0700 Subject: [PATCH 29/60] Fix settings icon on iOS 12, replace with new icon (#687) --- app/assets/svgs/settingsGear.js | 9 --------- app/assets/svgs/settingsIcon.js | 5 +++++ app/views/LocationTracking.js | 11 +++-------- 3 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 app/assets/svgs/settingsGear.js create mode 100644 app/assets/svgs/settingsIcon.js diff --git a/app/assets/svgs/settingsGear.js b/app/assets/svgs/settingsGear.js deleted file mode 100644 index ccf3f275f4..0000000000 --- a/app/assets/svgs/settingsGear.js +++ /dev/null @@ -1,9 +0,0 @@ -export default ` - - - - - - - -`; diff --git a/app/assets/svgs/settingsIcon.js b/app/assets/svgs/settingsIcon.js new file mode 100644 index 0000000000..be236d5193 --- /dev/null +++ b/app/assets/svgs/settingsIcon.js @@ -0,0 +1,5 @@ +export default ` + + + +`; diff --git a/app/views/LocationTracking.js b/app/views/LocationTracking.js index 329f41cd12..1667a8f1da 100644 --- a/app/views/LocationTracking.js +++ b/app/views/LocationTracking.js @@ -26,7 +26,7 @@ import BackgroundImageAtRisk from './../assets/images/backgroundAtRisk.png'; import exportImage from './../assets/images/export.png'; import foreArrow from './../assets/images/foreArrow.png'; import BackgroundImage from './../assets/images/launchScreenBackground.png'; -import SettingsGear from './../assets/svgs/settingsGear'; +import settingsIcon from './../assets/svgs/settingsIcon'; import StateAtRisk from './../assets/svgs/stateAtRisk'; import StateNoContact from './../assets/svgs/stateNoContact'; import StateUnknown from './../assets/svgs/stateUnknown'; @@ -302,12 +302,7 @@ class LocationTracking extends Component { }}> {/* Is there is a reason there's this imageless image tag here? Can we delete it? */} - + ); } @@ -525,7 +520,7 @@ const styles = StyleSheet.create({ position: 'absolute', top: 0, marginTop: '14%', - marginRight: '5%', + marginRight: '7%', alignSelf: 'flex-end', }, buttonContainer: { From 8fed82d4855b5620a99be0feeb0dce06dcc53c95 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Sun, 26 Apr 2020 15:17:43 -0700 Subject: [PATCH 30/60] yarn run-ios now runs pod install (#681) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c61c52bc6..25ef91f422 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "yarn && react-native start", "run-android": "react-native run-android", - "run-ios": "react-native run-ios", + "run-ios": "yarn install:pod && react-native run-ios", "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"", "install:pod": "cd ios && bundle install && bundle exec pod install", "postinstall": "patch-package; npx react-native-jetifier", From 68696067c00584b36074eaabda5148432aa19678 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Sun, 26 Apr 2020 15:17:57 -0700 Subject: [PATCH 31/60] Make nav back arrow color theme aware (#686) --- app/assets/svgs/backArrow.js | 14 ++++--------- app/components/NavigationBarWrapper.js | 21 ++++++++++++------- .../__snapshots__/Import.spec.js.snap | 19 ++++++++--------- .../__snapshots__/Licenses.spec.js.snap | 19 ++++++++--------- .../__tests__/__snapshots__/News.spec.js.snap | 19 ++++++++--------- .../__snapshots__/Settings.spec.js.snap | 19 ++++++++--------- 6 files changed, 54 insertions(+), 57 deletions(-) diff --git a/app/assets/svgs/backArrow.js b/app/assets/svgs/backArrow.js index 5bd6f593d3..ef65e23474 100644 --- a/app/assets/svgs/backArrow.js +++ b/app/assets/svgs/backArrow.js @@ -1,11 +1,5 @@ -export default ` - - - - - - - - - +export default ` + + + `; diff --git a/app/components/NavigationBarWrapper.js b/app/components/NavigationBarWrapper.js index 08994e33ae..75cb88afe8 100644 --- a/app/components/NavigationBarWrapper.js +++ b/app/components/NavigationBarWrapper.js @@ -34,9 +34,9 @@ const NavigationBarWrapper = ({ children, title, onBackPress }) => {
                - onBackPress()}> - - + onBackPress()}> + + {title}
                {children} @@ -64,24 +64,29 @@ const themeNavBarBorder = ({ theme }) => theme.navBarBorder || Colors.NAV_BAR_VIOLET; const Header = styled.View` + align-items: center; background-color: ${themeNavBar}; border-bottom-color: ${themeNavBarBorder}; border-bottom-width: 1px; flex-direction: row; `; +const themeOnNavBar = ({ theme }) => theme.onNavBar || Colors.WHITE; + const Title = styled.Text` align-self: center; - color: ${Colors.WHITE}; + color: ${themeOnNavBar}; font-family: IBMPlexSans-Medium; font-size: ${26 * widthScale + 'px'}; + line-height: 34px; position: absolute; - padding-horizontal: 20; + padding-horizontal: 20px; text-align: center; width: 100%; + letter-spacing: -0.3px; `; -const BackArrow = styled.TouchableOpacity` +const BackArrowIcon = styled.TouchableOpacity` align-items: center; height: 55px; justify-content: center; @@ -89,9 +94,11 @@ const BackArrow = styled.TouchableOpacity` z-index: 1; `; -const BackArrowIcon = styled(SvgXml)` +const BackArrowSvg = styled(SvgXml)` height: 18px; width: 18px; + color: ${Colors.WHITE}; + opacity: 0.4; `; NavigationBarWrapper.propTypes = { diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index a2949dee4a..cd5479399f 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -27,6 +27,7 @@ Array [ - - - - - - - - - + xml=" + + + " /> @@ -83,6 +80,8 @@ Array [ "color": "#FFF", "fontFamily": "IBMPlexSans-Medium", "fontSize": 26, + "letterSpacing": -0.3, + "lineHeight": 34, "paddingHorizontal": 20, "position": "absolute", "textAlign": "center", diff --git a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap index 5a0f41a7d6..d4d9ee9eaf 100644 --- a/app/views/__tests__/__snapshots__/Licenses.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Licenses.spec.js.snap @@ -35,6 +35,7 @@ exports[`renders correctly 1`] = ` - - - - - - - - - + xml=" + + + " /> @@ -84,6 +81,8 @@ exports[`renders correctly 1`] = ` "color": "#FFF", "fontFamily": "IBMPlexSans-Medium", "fontSize": 26, + "letterSpacing": -0.3, + "lineHeight": 34, "paddingHorizontal": 20, "position": "absolute", "textAlign": "center", diff --git a/app/views/__tests__/__snapshots__/News.spec.js.snap b/app/views/__tests__/__snapshots__/News.spec.js.snap index 5acbd573d2..333f1fa1dd 100644 --- a/app/views/__tests__/__snapshots__/News.spec.js.snap +++ b/app/views/__tests__/__snapshots__/News.spec.js.snap @@ -62,6 +62,7 @@ exports[`renders correctly 1`] = ` - - - - - - - - - + xml=" + + + " /> @@ -111,6 +108,8 @@ exports[`renders correctly 1`] = ` "color": "#FFF", "fontFamily": "IBMPlexSans-Medium", "fontSize": 26, + "letterSpacing": -0.3, + "lineHeight": 34, "paddingHorizontal": 20, "position": "absolute", "textAlign": "center", diff --git a/app/views/__tests__/__snapshots__/Settings.spec.js.snap b/app/views/__tests__/__snapshots__/Settings.spec.js.snap index d013203fbc..12bef95b4c 100644 --- a/app/views/__tests__/__snapshots__/Settings.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Settings.spec.js.snap @@ -35,6 +35,7 @@ exports[`renders correctly 1`] = ` - - - - - - - - - + xml=" + + + " /> @@ -84,6 +81,8 @@ exports[`renders correctly 1`] = ` "color": "#FFF", "fontFamily": "IBMPlexSans-Medium", "fontSize": 26, + "letterSpacing": -0.3, + "lineHeight": 34, "paddingHorizontal": 20, "position": "absolute", "textAlign": "center", From 0cca14b357511cb2b2cbf2f7839f85e260751028 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Sun, 26 Apr 2020 15:56:32 -0700 Subject: [PATCH 32/60] Clean up more eslint issues (#678) --- .eslintrc.js | 14 ++++++++++ __mocks__/react-native-i18n.js | 2 +- app/components/ButtonWrapper.js | 3 +- app/components/NativePicker.js | 9 +++--- app/helpers/__tests__/Intersect.spec.js | 13 ++------- app/helpers/tests/__mocks__/General.js | 4 +-- app/services/BroadcastingService.js | 28 +++++++++---------- .../__tests__/LocationService.spec.js | 20 +++++-------- app/views/About.js | 13 +++++---- app/views/Import.js | 28 ------------------- app/views/News.js | 1 - app/views/__tests__/LocationTracking.spec.js | 7 +++-- .../__snapshots__/Settings.spec.js.snap | 6 ++-- jest/setupFile.js | 2 +- package.json | 1 + 15 files changed, 64 insertions(+), 87 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b817fe75d3..c86b6f8434 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -65,4 +65,18 @@ module.exports = { 'react/react-in-jsx-scope': 2, // Prevent missing React when using JSX 'react/self-closing-comp': 2, // Prevent extra closing tags for components without children }, + overrides: [ + { + files: ['*.spec.js'], // Or *.test.js + rules: { + 'react-native/no-raw-text': 0, + }, + }, + { + files: ['e2e/**/*.js'], // Or *.test.js + rules: { + 'jest/expect-expect': 0, // these files do expectations inside page objects + }, + }, + ], }; diff --git a/__mocks__/react-native-i18n.js b/__mocks__/react-native-i18n.js index 608a07abbc..24d41a2204 100644 --- a/__mocks__/react-native-i18n.js +++ b/__mocks__/react-native-i18n.js @@ -1,5 +1,5 @@ export function getLanguages() { - return new Promise((resolve, reject) => { + return new Promise(resolve => { process.nextTick(() => resolve(['en-US'])); }); } diff --git a/app/components/ButtonWrapper.js b/app/components/ButtonWrapper.js index c4a7daa5ac..54093e3e0c 100644 --- a/app/components/ButtonWrapper.js +++ b/app/components/ButtonWrapper.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import { Dimensions, StyleSheet, View } from 'react-native'; +import colors from '../constants/colors'; import fontFamily from '../constants/fonts'; import Button from './Button'; @@ -28,7 +29,7 @@ const styles = StyleSheet.create({ lineHeight: 19, letterSpacing: 0, textAlign: 'center', - color: '#ffffff', + color: colors.WHITE, }, buttonContainer: { flexDirection: 'row', diff --git a/app/components/NativePicker.js b/app/components/NativePicker.js index bc7e92a5b1..3d6a1ad9d3 100644 --- a/app/components/NativePicker.js +++ b/app/components/NativePicker.js @@ -8,6 +8,7 @@ import { } from 'react-native'; import { Typography } from '../components/Typography'; +import Colors from '../constants/colors'; import languages from '../locales/languages'; /** @@ -79,25 +80,25 @@ export default class NativePicker extends Component { visible={this.state.modalVisible}> this.setState({ modalVisible: false })}> - + this.setState({ modalVisible: false })}> { expect(resultBins).toEqual(expectedBins); }); - /** - * A daylight savings time change should cause either a 23 or 25 hour day. - * - * TODO: Once we've figured out a good way to mock the timezone and DST, make this test work - */ - //it('is not affectd by daylight saving changes', () => { - // - // TODO: Implement this test. Note that until we figure out a good way to mock both the - // timezone as well as DST, this test can't be done in a meaningful way. - // - //}); + // A daylight savings time change should cause either a 23 or 25 hour day. + // TODO: Once we've figured out a good way to mock the timezone and DST, make this test work }); /** diff --git a/app/helpers/tests/__mocks__/General.js b/app/helpers/tests/__mocks__/General.js index ceb75f5829..0cc626c4d1 100644 --- a/app/helpers/tests/__mocks__/General.js +++ b/app/helpers/tests/__mocks__/General.js @@ -1,11 +1,11 @@ export function GetStoreData(data) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { resolve(data); }); } export function SetStoreData() { - return new Promise((resolve, reject) => { + return new Promise(resolve => { resolve({}); }); } diff --git a/app/services/BroadcastingService.js b/app/services/BroadcastingService.js index f4e6244828..3a428d8722 100644 --- a/app/services/BroadcastingService.js +++ b/app/services/BroadcastingService.js @@ -8,10 +8,10 @@ import { CONTACT_DATA, MY_UUIDs } from '../constants/storage'; import { GetStoreData, SetStoreData } from '../helpers/General'; import { isPlatformAndroid, nowStr } from '../Util'; -var currentUUID = null; -var onDeviceFound = null; -var onBTStatusChange = null; -var lastSeen = {}; +let currentUUID = null; +let onDeviceFound = null; +let onBTStatusChange = null; +let lastSeen = {}; const c5_MINS = 1000 * 60 * 5; const c28_DAYS = 1000 * 60 * 60 * 24 * 28; @@ -24,7 +24,7 @@ const MANUFACTURER_DATA = [12, 23, 56]; * Check if the contact is new in the last 5 mins. */ function isNewContact(contact) { - var nowLocal = new Date().getTime(); + let nowLocal = new Date().getTime(); if ( lastSeen[contact['uuid']] && lastSeen[contact['uuid']] > nowLocal - c5_MINS @@ -60,13 +60,13 @@ function saveContact(contact) { } // Always work in UTC, not the local time in the contactData - var nowUTC = new Date().toISOString(); - var unixtimeUTC = Date.parse(nowUTC); + let nowUTC = new Date().toISOString(); + let unixtimeUTC = Date.parse(nowUTC); // Curate the list of contacts, only keep the last 28 days let curated = filterAfter(contactArray, unixtimeUTC - c28_DAYS); - var uuid_time = { + let uuid_time = { uuid: contact['uuid'], time: unixtimeUTC, }; @@ -94,13 +94,13 @@ function saveMyUUID(me) { } // Always work in UTC, not the local time in the contactData - var nowUTC = new Date().toISOString(); - var unixtimeUTC = Date.parse(nowUTC); + let nowUTC = new Date().toISOString(); + let unixtimeUTC = Date.parse(nowUTC); // Curate the list of points, only keep the last 28 days let curated = filterAfter(myUUIDArray, unixtimeUTC - c28_DAYS); - var uuid_time = { + let uuid_time = { uuid: me['uuid'], time: unixtimeUTC, }; @@ -128,7 +128,7 @@ function loadLastUUIDAndBroadcast() { myUUIDArray[myUUIDArray.length - 1].uuid, 'Loading last uuid', ); - var lastUUID = myUUIDArray[myUUIDArray.length - 1].uuid; + let lastUUID = myUUIDArray[myUUIDArray.length - 1].uuid; broadcast(lastUUID); } else { generateNewUUIDAndBroadcast(); @@ -295,7 +295,7 @@ export default class BroadcastingServices { BroadcastingServices.askBTActive(); } }) - .catch(error => { + .catch(() => { console.log('[Bluetooth]', nowStr(), currentUUID, 'BT Not Enabled'); }); } @@ -321,7 +321,7 @@ export default class BroadcastingServices { BroadcastingServices.stopAndClearCallbacks(); } }) - .catch(error => { + .catch(() => { console.log('[Bluetooth]', nowStr(), currentUUID, 'BT Not Enabled'); }); } diff --git a/app/services/__tests__/LocationService.spec.js b/app/services/__tests__/LocationService.spec.js index fe62989d0b..e1ebcf3f9f 100644 --- a/app/services/__tests__/LocationService.spec.js +++ b/app/services/__tests__/LocationService.spec.js @@ -1,19 +1,13 @@ -import LocationService, { LocationData } from '../LocationService'; import * as General from '../../helpers/General'; +import { LocationData } from '../LocationService'; function mockGetStoreData(data) { General.GetStoreData = () => { - return new Promise((resolve, reject) => { - process.nextTick(() => - resolve(data) - ); + return new Promise(resolve => { + process.nextTick(() => resolve(data)); }); }; -}; - -function mockSetStoreData(name, data) { - return General.SetStoreData = jest.fn(); -}; +} describe('LocationData class', () => { let locationData; @@ -23,7 +17,7 @@ describe('LocationData class', () => { }); it('has the correct time interval', () => { - expect(locationData.locationInterval).toBe(60000*5); + expect(locationData.locationInterval).toBe(60000 * 5); }); it('parses the location data', async () => { @@ -34,7 +28,7 @@ describe('LocationData class', () => { expect(data.length).toBe(5); }); - it('parses the location data', async () => { + it('parses the location data point stats', async () => { mockGetStoreData('[2,4,6,8,10]'); const data = await locationData.getPointStats(); @@ -43,4 +37,4 @@ describe('LocationData class', () => { expect(data.lastPoint).toBe(10); expect(data.pointCount).toBe(5); }); -}) +}); diff --git a/app/views/About.js b/app/views/About.js index 1c96703c43..5547dc0dc0 100644 --- a/app/views/About.js +++ b/app/views/About.js @@ -5,7 +5,6 @@ import { Platform, ScrollView, StyleSheet, - Text, View, } from 'react-native'; import { TouchableOpacity } from 'react-native-gesture-handler'; @@ -107,6 +106,7 @@ class AboutScreen extends Component { {this.state.tapCount > 3 && ( In exposure demo mode, tap to disable @@ -133,7 +134,7 @@ class AboutScreen extends Component { - Version:{' '} + {languages.t('about.version')} {packageJson.version} @@ -141,19 +142,19 @@ class AboutScreen extends Component { - OS: + + {languages.t('about.operating_system_abbr')} + - {' '} {Platform.OS + ' v' + Platform.Version} - Dimensions: + {languages.t('about.dimensions')} - {' '} {Math.trunc(Dimensions.get('screen').width) + ' x ' + Math.trunc(Dimensions.get('screen').height)} diff --git a/app/views/Import.js b/app/views/Import.js index 2cd93434ca..f28877d14c 100644 --- a/app/views/Import.js +++ b/app/views/Import.js @@ -114,13 +114,6 @@ const ImportScreen = props => { }; const styles = StyleSheet.create({ - // Container covers the entire screen - container: { - flex: 1, - flexDirection: 'column', - color: colors.PRIMARY_TEXT, - backgroundColor: colors.WHITE, - }, subHeaderTitle: { textAlign: 'center', fontWeight: 'bold', @@ -154,27 +147,6 @@ const styles = StyleSheet.create({ textAlign: 'center', color: colors.WHITE, }, - headerContainer: { - flexDirection: 'row', - height: 60, - borderBottomWidth: 1, - borderBottomColor: 'rgba(189, 195, 199,0.6)', - alignItems: 'center', - }, - backArrowTouchable: { - width: 60, - height: 60, - paddingTop: 21, - paddingLeft: 20, - }, - backArrow: { - height: 18, - width: 18.48, - }, - headerTitle: { - fontSize: 24, - fontFamily: fontFamily.primaryRegular, - }, sectionDescription: { fontSize: 16, lineHeight: 24, diff --git a/app/views/News.js b/app/views/News.js index bf5224cc9e..7a7158f2a7 100644 --- a/app/views/News.js +++ b/app/views/News.js @@ -4,7 +4,6 @@ import { BackHandler, Dimensions, StyleSheet, - Text, View, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; diff --git a/app/views/__tests__/LocationTracking.spec.js b/app/views/__tests__/LocationTracking.spec.js index 650de180b0..86c8fb3a3a 100644 --- a/app/views/__tests__/LocationTracking.spec.js +++ b/app/views/__tests__/LocationTracking.spec.js @@ -1,11 +1,14 @@ import 'react-native'; + +import { render, wait } from '@testing-library/react-native'; import React from 'react'; -import {render, wait} from '@testing-library/react-native'; + import LocationTracking from '../LocationTracking'; // TODO(#373): Skipped due to worker not exiting, hanging CI action +// eslint-disable-next-line jest/no-disabled-tests it.skip('renders correctly', async () => { - const {asJSON} = render(); + const { asJSON } = render(); await wait(); diff --git a/app/views/__tests__/__snapshots__/Settings.spec.js.snap b/app/views/__tests__/__snapshots__/Settings.spec.js.snap index 12bef95b4c..08651893f7 100644 --- a/app/views/__tests__/__snapshots__/Settings.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Settings.spec.js.snap @@ -242,7 +242,7 @@ exports[`renders correctly 1`] = ` focusable={true} style={ Object { - "backgroundColor": "#000000", + "backgroundColor": "#000", "flex": 1, "opacity": 0.2, } @@ -252,7 +252,7 @@ exports[`renders correctly 1`] = ` { return { createAppContainer: jest .fn() - .mockReturnValue(function NavigationContainer(props) { + .mockReturnValue(function NavigationContainer() { return null; }), createDrawerNavigator: jest.fn(), diff --git a/package.json b/package.json index 25ef91f422..176b71ee38 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "run-ios": "yarn install:pod && react-native run-ios", "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"", "install:pod": "cd ios && bundle install && bundle exec pod install", + "lint": "eslint ./", "postinstall": "patch-package; npx react-native-jetifier", "postversion": "react-native-version", "format:all": "prettier --write ./app/**/*.js", From 8e684228a0d543964df402269335470de1d45927 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Mon, 27 Apr 2020 16:51:03 -0500 Subject: [PATCH 33/60] Auto add authorities (#587) --- __mocks__/react-native-push-notification.js | 1 + app/Entry.js | 1 - app/constants/storage.js | 1 + app/helpers/General.js | 4 +- app/helpers/Intersect.js | 4 +- app/locales/en.json | 22 +- app/services/BackgroundTaskService.js | 2 + app/services/HCAService.js | 255 ++++++++++ app/services/LocationService.js | 89 +++- app/services/__mocks__/mockHCA.js | 45 ++ app/services/__mocks__/mockUserLocHistory.js | 25 + app/services/__tests__/HCAService.spec.js | 306 ++++++++++++ app/views/ChooseProvider.js | 244 +++++----- app/views/LocationTracking.js | 39 +- .../__snapshots__/Settings.spec.js.snap | 4 +- app/views/onboarding/Onboarding5.js | 444 ++++++++++++------ e2e/helpers/language.js | 17 +- e2e/helpers/onboarding.js | 25 +- .../InsufficientLocationPermissions.spec.js | 14 +- .../NoAuthSubscriptionPermission.spec.js | 24 + e2e/onboarding/NoLocationPermissions.spec.js | 15 +- .../NoNotificationsPermissions.spec.js | 14 +- e2e/onboarding/Onboarding.spec.js | 21 +- e2e/onboarding/Permissions.spec.js | 15 +- e2e/onboarding/UnsignedEula.spec.js | 14 +- e2e/pages/EnableAuthoritySubscription.po.js | 24 + e2e/pages/EnableLocation.po.js | 7 + e2e/pages/EnableNotifications.po.js | 22 + e2e/pages/FinishSetup.po.js | 4 +- e2e/pages/Onboarding5.po.js | 34 -- 30 files changed, 1308 insertions(+), 428 deletions(-) create mode 100644 app/services/HCAService.js create mode 100644 app/services/__mocks__/mockHCA.js create mode 100644 app/services/__mocks__/mockUserLocHistory.js create mode 100644 app/services/__tests__/HCAService.spec.js create mode 100644 e2e/onboarding/NoAuthSubscriptionPermission.spec.js create mode 100644 e2e/pages/EnableAuthoritySubscription.po.js create mode 100644 e2e/pages/EnableNotifications.po.js delete mode 100644 e2e/pages/Onboarding5.po.js diff --git a/__mocks__/react-native-push-notification.js b/__mocks__/react-native-push-notification.js index 8ce3bda84e..3bb0c4f60c 100644 --- a/__mocks__/react-native-push-notification.js +++ b/__mocks__/react-native-push-notification.js @@ -4,4 +4,5 @@ export default { onNotification: jest.fn(), addEventListener: jest.fn(), requestPermissions: jest.fn(), + localNotification: jest.fn(), }; diff --git a/app/Entry.js b/app/Entry.js index 9fbbb7d690..d6bb81ec23 100644 --- a/app/Entry.js +++ b/app/Entry.js @@ -34,7 +34,6 @@ class Entry extends Component { componentDidMount() { GetStoreData('ONBOARDING_DONE') .then(onboardingDone => { - console.log(onboardingDone); this.setState({ initialRouteName: onboardingDone, }); diff --git a/app/constants/storage.js b/app/constants/storage.js index 1a97b37fad..2e0da49f63 100644 --- a/app/constants/storage.js +++ b/app/constants/storage.js @@ -8,3 +8,4 @@ export const DEBUG_MODE = 'DEBUG_MODE'; export const AUTHORITY_NEWS = 'AUTHORITY_NEWS'; export const LAST_CHECKED = 'LAST_CHECKED'; export const AUTHORITY_SOURCE_SETTINGS = 'AUTHORITY_SOURCE_SETTINGS'; +export const ENABLE_HCA_AUTO_SUBSCRIPTION = 'ENABLE_HCA_AUTO_SUBSCRIPTION'; diff --git a/app/helpers/General.js b/app/helpers/General.js index cbfb0b3abd..5e3cc7b96d 100644 --- a/app/helpers/General.js +++ b/app/helpers/General.js @@ -2,8 +2,7 @@ import AsyncStorage from '@react-native-community/async-storage'; import DocumentPicker from 'react-native-document-picker'; /** - * Get Data from Store - + * Get data from store * * @param {string} key * @param {boolean} isString @@ -25,7 +24,6 @@ export async function GetStoreData(key, isString = true) { /** * Set data from store - * * @param {string} key * @param {object} item diff --git a/app/helpers/Intersect.js b/app/helpers/Intersect.js index 196f7636a2..56f1ae9dd1 100644 --- a/app/helpers/Intersect.js +++ b/app/helpers/Intersect.js @@ -276,9 +276,9 @@ export function checkIntersect() { */ async function asyncCheckIntersect() { // first things first ... is it time to actually try the intersection? - let last_checked_ms = Number(await GetStoreData(LAST_CHECKED)); + let lastCheckedMs = Number(await GetStoreData(LAST_CHECKED)); if ( - last_checked_ms + MIN_CHECK_INTERSECT_INTERVAL * 60 * 1000 > + lastCheckedMs + MIN_CHECK_INTERSECT_INTERVAL * 60 * 1000 > dayjs().valueOf() ) return null; diff --git a/app/locales/en.json b/app/locales/en.json index 4eed353468..c9e810f810 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -6,13 +6,22 @@ "authorities_desc": "Choose trusted healthcare authorities in your area to obtain exposure data. Either select a name from the global registry, or enter the web address provided by an authority which has implemented Safe Paths.", "authorities_input_placeholder": "Paste your URL here", "authorities_no_sources": "No data source yet", + "authorities_new_in_area_title": "New Healthcare Authority", + "authorities_new_in_area_title_plural": "New Healthcare Authorities", + "authorities_new_in_area_msg": "Subscribe to {{count}} new trusted Healthcare Authority in your location", + "authorities_new_in_area_msg_plural": "Subscribe to {{count}} new trusted Healthcare Authorities in your location", + "authorities_new_subcription_title": "{{numAuthories}} New Subscription", + "authorities_new_subcription_title_plural": "{{numAuthories}} New Subscriptions", + "authorities_new_subcription_msg": "You have been subscribed to {{count}} new authority in your location", + "authorities_new_subcription_msg_plural": "You have been subscribed to {{count}} new authorities in your location", "authorities_removal_alert_cancel": "Cancel", "authorities_removal_alert_desc": "Are you sure you want to remove this authority data source?", "authorities_removal_alert_proceed": "Proceed", - "authorities_removal_alert_title": "Remove authority", + "authorities_removal_alert_title": "Remove Authority", "authorities_title": "Trusted Sources", - "choose_provider_subtitle": "To be informed of exposures you will need to subscribe to a health authority.", - "choose_provider_title": "Choose health authority", + "auto_subscribe_checkbox": "Enable auto subscription", + "choose_provider_subtitle": "To be informed of exposures you will need to subscribe to a Health Authority.", + "choose_provider_title": "Choose Health Authority", "commitment": "Commitment", "commitment_para": "Safe Paths securely records and checks your interaction with people using your location. Your data will NEVER leave your phone without your consent.", "default_news_site_name": "Safe Paths News", @@ -20,6 +29,8 @@ "event_history_title": "Exposure history", "export_para_1": "If you test positive for COVID-19, please do your part by sharing your location history with local authorities.", "export_para_2": "Location is shared as a simple list of times and places, no additional information.", + "filter_authorities_by_gps_history": "Filter by your locations", + "enter_authority_url": "Enter or paste URL", "home_at_risk_header": "You May Be Exposed", "home_at_risk_subsubtext": "This does not mean you are infected.", "home_at_risk_subtext": "Based on your GPS history, it is possible you were in contact with or close to someone diagnosed with COVID-19.", @@ -42,6 +53,7 @@ "launch_done_subheader": "You’re ready to roll. Remember, you can always update your preferences later.", "launch_enable_location": "Enable Location", "launch_enable_notif": "Enable Notifications", + "launch_enable_auto_subscription": "Enable auto subscription", "launch_finish_set_up": "Finish Setup", "launch_get_started": "Get Started", "launch_location_access": "Location access", @@ -51,6 +63,9 @@ "launch_notif_header": "Notifications will let you know if you cross paths with an infected person.", "launch_notif_subheader": "We won't bother you except to share updates on your potential exposure risks.", "launch_notification_access": "Allow notifications", + "launch_authority_header": "Healthcare Authorities will provide your device with the local data to know if you have crossed paths with an infected person", + "launch_authority_subheader": "Automatically subscribe to receive the latest updates from Healthcare Authorities in your area.", + "launch_authority_access": "Subscribe to nearby Health Authorities", "launch_screen1_header": "The way back to normal starts here.", "launch_screen2_header": "Get notified if you cross paths with someone later diagnosed for COVID-19.", "launch_screen2_subheader": "Knowledge is power.", @@ -79,6 +94,7 @@ "see_exposure_history": "See exposure history", "settings_title": "Dashboard", "share_location_data": "Share location data", + "skip_this_step": "Skip this step", "team": "Team", "team_para": "Our team is composed of a consortium of epidemiologists, engineers, data scientists, digital privacy evangelists, professors and researchers from reputable institutions, including: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young and Link Ventures.", "terms_of_use": "Terms of use", diff --git a/app/services/BackgroundTaskService.js b/app/services/BackgroundTaskService.js index 3cddd7de55..529ea4085c 100644 --- a/app/services/BackgroundTaskService.js +++ b/app/services/BackgroundTaskService.js @@ -2,9 +2,11 @@ import BackgroundFetch from 'react-native-background-fetch'; import { INTERSECT_INTERVAL } from '../constants/history'; import { checkIntersect } from '../helpers/Intersect'; +import { HCAService } from '../services/HCAService'; export function executeTask() { checkIntersect(); + HCAService.findNewAuthorities(); } export default class BackgroundTaskServices { diff --git a/app/services/HCAService.js b/app/services/HCAService.js new file mode 100644 index 0000000000..795cfb28b0 --- /dev/null +++ b/app/services/HCAService.js @@ -0,0 +1,255 @@ +import Yaml from 'js-yaml'; +import PushNotification from 'react-native-push-notification'; +import RNFetchBlob from 'rn-fetch-blob'; + +import { AUTHORITIES_LIST_URL } from '../constants/authorities'; +import { + AUTHORITY_SOURCE_SETTINGS, + ENABLE_HCA_AUTO_SUBSCRIPTION, +} from '../constants/storage'; +import { GetStoreData, SetStoreData } from '../helpers/General'; +import languages from '../locales/languages'; +import { LocationData } from './LocationService'; + +/** + * Singleton class to interact with health care authority data + */ +class HCAService { + /** + * Fetches the raw YAML file containing a list of all + * of the registered Health Care Authorities. + * Saves the response as a cached file for performance. + * @returns {void} + */ + async fetchAuthoritiesYaml() { + return await RNFetchBlob.config({ + fileCache: true, + }).fetch('GET', AUTHORITIES_LIST_URL); + } + + /** + * Fetches the list of all registed Health Care Authorities + * @returns {Array} List of health care authorities from the global registry + */ + async getAuthoritiesList() { + let authorities = []; + + try { + const result = await this.fetchAuthoritiesYaml(); + const list = await RNFetchBlob.fs.readFile(result.path(), 'utf8'); + authorities = Yaml.safeLoad(list).Authorities; + } catch (err) { + console.error(err); + } + + return authorities; + } + + /** + * Get the list of Health Care Authorities that a user has saved + * @returns {Array} List of health care authorities from storage + */ + async getUserAuthorityList() { + return await GetStoreData(AUTHORITY_SOURCE_SETTINGS, false); + } + + /** + * Takes an array of one or more authorities and adds it to the list + * in storage that the user is subscribed to. + * @param {newAuthorities} Array healthcare authoritiy objects + * @returns void + */ + async appendToAuthorityList(newAuthorities) { + const authorities = (await this.getUserAuthorityList()) || []; + await SetStoreData(AUTHORITY_SOURCE_SETTINGS, [ + ...authorities, + ...newAuthorities, + ]); + } + + /** + * Checks if a user has saved any Health Care Authorities + * + * @returns {boolean} + */ + async hasSavedAuthorities() { + const authorities = await this.getUserAuthorityList(); + return authorities && authorities.length > 0; + } + + /** + * Alerts a user that there are new Healthcare Authorities in their region. + * Includes information on the number of Authorities in their current location. + * + * @param {count} number new authorities + * @returns {void} + */ + async pushAlertNewAuthoritesFromLoc(count) { + await PushNotification.localNotification({ + title: languages.t('label.authorities_new_in_area_title', { count }), + message: languages.t('label.authorities_new_in_area_msg', { count }), + }); + } + + /** + * Alerts a user that they have been subscribed to new Healthcare Authorities + * in their region. Includes information on the number of Authorities in + * their current location. + * + * @param {count} number new authorities + * @returns {void} + */ + async pushAlertNewSubscribedAuthorities(count) { + await PushNotification.localNotification({ + title: languages.t('label.authorities_new_subcription_title', { count }), + message: languages.t('label.authorities_new_subcription_msg', { count }), + }); + } + + /** + * Returns the `url` value for an authority + * @param Authority Healthcare Authority object + * @returns {string} + */ + getAuthorityUrl(authority) { + const authorityName = Object.keys(authority)[0]; + const urlKey = authority[authorityName][0]; + return urlKey && urlKey['url']; + } + + /** + * Returns the `bounds` value for an authority + * @param {authority} Authority Healthcare Authority object + * @returns {{bounds: {ne: {latitude: number, longitude: number}}, {sw: {latitude: number, longitude: number}}}} + */ + getAuthorityBounds(authority) { + const authorityName = Object.keys(authority)[0]; + const boundsKey = authority[authorityName][1]; + return boundsKey && boundsKey['bounds']; + } + + /** + * Checks if a given point is inside the bounds of the given authority + * @param {point} Object contains a `latitude` and `longitude` field + * @param {authority} Authority Healthcare Authority object + * @returns {boolean} + */ + isPointInAuthorityBounds(point, authority) { + const locHelper = new LocationData(); + const bounds = this.getAuthorityBounds(authority); + + return bounds && locHelper.isPointInBoundingBox(point, bounds); + } + + /** + * Iterates over the full list of authorities and checks + * if there is any GPS point in the user's full 28-day location history + * that is within the bounds of the authority. + * + * @returns {[{authority_name: [{url: string}, {bounds: Object}]}]} List of health care authorities + */ + async getAuthoritiesFromUserLocHistory() { + const locData = await new LocationData().getLocationData(); + const authorities = await this.getAuthoritiesList(); + + return authorities.filter(authority => + locData.some(point => this.isPointInAuthorityBounds(point, authority)), + ); + } + + /** + * Gets the most recent location of the user and returns a list of + * all Healthcare Authorities whose bounds contain the user's current location, + * filtering out any Authorities the user has already subscribed to. + * + * @returns {[{authority_name: [{url: string}, {bounds: Object}]}]} list of Healthcare Authorities + */ + async getNewAuthoritiesInUserLoc() { + const mostRecentUserLoc = await new LocationData().getMostRecentUserLoc(); + const authoritiesList = await this.getAuthoritiesList(); + const userAuthorities = await this.getUserAuthorityList(); + + return authoritiesList.filter( + authority => + this.isPointInAuthorityBounds(mostRecentUserLoc, authority) && + !userAuthorities.includes(authority), + ); + } + + /** + * Subscribes a user to the provided list of authorities + * @param {newAuthorities} Array array of healthcare authorities + * @returns {void} + */ + async pushAlertNewSubscriptions(newAuthorities) { + await this.appendToAuthorityList(newAuthorities); + await this.pushAlertNewSubscribedAuthorities(newAuthorities.length); + } + + /** + * Prompt a user to add a Health Authority if they are in the bounds + * of a healthcare authority that they have not yet subscribed to. + * + * This will trigger a push notification. + * + * @returns {void} + */ + async findNewAuthorities() { + const newAuthorities = await this.getNewAuthoritiesInUserLoc(); + + if (newAuthorities.length > 0) { + if (this.isAutosubscriptionEnabled()) { + await this.pushAlertNewSubscriptions(newAuthorities); + } else { + await this.pushAlertNewAuthoritesFromLoc(newAuthorities.length); + } + } + } + + /** + * Checks if the user has explicitly approved or denied the auto subscribe + * feature. When pulling from async storage, if the key has not yet been set, + * the value will be null. + * + * @returns {boolean} + */ + async hasUserSetSubscription() { + const permission = await GetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, true); + + if (permission === null) { + return false; + } else { + return true; + } + } + + /** + * Check if the user has opted in to auto subscribe to new Healthcare + * Authorities in their area. + * + * @returns {boolean} + */ + async isAutosubscriptionEnabled() { + return (await GetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, true)) === 'true'; + } + + /** + * Enable auto subscription to new Healthcare Authorities in the user's area. + * @returns {void} + */ + async enableAutoSubscription() { + await SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, true); + } + + /** + * Disable auto subscription to new Healthcare Authorities in the user's area. + * @returns {void} + */ + async disableAutoSubscription() { + await SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, false); + } +} + +const singleton = new HCAService(); + +export { singleton as HCAService }; diff --git a/app/services/LocationService.js b/app/services/LocationService.js index da3a52e212..c5e9b58867 100644 --- a/app/services/LocationService.js +++ b/app/services/LocationService.js @@ -23,15 +23,15 @@ export class LocationData { this.maxBackfillTime = 60000 * 60 * 24; // Time (in milliseconds). 60000 * 60 * 8 = 24 hours } - getLocationData() { - return GetStoreData(LOCATION_DATA).then(locationArrayString => { - let locationArray = []; - if (locationArrayString !== null) { - locationArray = JSON.parse(locationArrayString); - } + async getLocationData() { + const locationArrayString = await GetStoreData(LOCATION_DATA); + let locationArray = []; - return locationArray; - }); + if (locationArrayString !== null) { + locationArray = JSON.parse(locationArrayString); + } + + return locationArray; } async getPointStats() { @@ -133,12 +133,83 @@ export class LocationData { SetStoreData(LOCATION_DATA, curated); }); } + + /** + * Validates that `point` has both a latitude and longitude field + * @param {*} point - Object to validate + */ + isValidPoint(point) { + if (!point.latitude && !point.latitude === 0) { + console.error('`point` param must have a latitude field'); + return false; + } + + if (!point.longitude && !point.longitude === 0) { + console.error('`point` param must have a longitude field'); + return false; + } + + return true; + } + + /** + * Validates that an object is a valid geographic bounding box. + * A valid box has a `ne` and `sw` field that each contain a valid GPS point + * @param {*} region - Object to validate + */ + isValidBoundingBox(region) { + if (!region.ne || !this.isValidPoint(region.ne)) { + console.error(`invalid 'ne' field for bounding box: ${region.ne}`); + return false; + } + + if (!region.sw || !this.isValidPoint(region.sw)) { + console.error(`invalid 'ne' field for bounding box: ${region.sw}`); + return false; + } + + return true; + } + + /** + * Returns the most recent point of location data for a user. + * This is the last item in the location data array. + */ + async getMostRecentUserLoc() { + const locData = await this.getLocationData(); + return locData[locData.length - 1]; + } + + /** + * Given a GPS coordinate, check if it is within the bounding + * box of a region. + * @param {*} point - Object with a `latitude` and `longitude` field + * @param {*} region - Object with a `ne` and `sw` field that each contain a GPS point + */ + isPointInBoundingBox(point, region) { + if (!this.isValidPoint(point) || !this.isValidBoundingBox(region)) { + return false; + } else { + const { latitude: pointLat, longitude: pointLon } = point; + const { latitude: neLat, longitude: neLon } = region.ne; + const { latitude: swLat, longitude: swLon } = region.sw; + + const [latMax, latMin] = neLat > swLat ? [neLat, swLat] : [swLat, neLat]; + const [lonMax, lonMin] = neLon > swLon ? [neLon, swLon] : [swLon, neLon]; + + return ( + pointLat < latMax && + pointLat > latMin && + pointLon < lonMax && + pointLon > lonMin + ); + } + } } export default class LocationServices { static start() { const locationData = new LocationData(); - // handles edge cases around Android where start might get called again even though // the service is already created. Make sure the listeners are still bound and exit if (isBackgroundGeolocationConfigured) { diff --git a/app/services/__mocks__/mockHCA.js b/app/services/__mocks__/mockHCA.js new file mode 100644 index 0000000000..1ae22b464f --- /dev/null +++ b/app/services/__mocks__/mockHCA.js @@ -0,0 +1,45 @@ +import Yaml from 'js-yaml'; + +/** + * Note: Because we are mocking the `readFile` method of RNFetchBlob, + * we are exporting the YAML config as the string that would be returned + * from `readFile`. + */ +export const validYaml = ` +Authorities: + - Test Authority: + - { + url: "https://raw.githack.com/tripleblindmarket/safe-places/develop/examples/safe-paths.json", + } + - bounds: + { + "ne": { "latitude": 36.42025904738132, "longitude": -121.93670068664551 }, + "sw": { "latitude": 38.29988330010084, "longitude": -123.2516993133545 }, + } +`; + +export const invalidYamlWithoutBounds = ` +Authorities: + - Test Authority: + - { + url: "https://raw.githack.com/tripleblindmarket/safe-places/develop/examples/safe-paths.json", + } +`; + +export const invalidYamlWithoutUrl = ` +Authorities: + - Test Authority: + - bounds: + { + "ne": { "latitude": 36.42025904738132, "longitude": -121.93670068664551 }, + "sw": { "latitude": 38.29988330010084, "longitude": -123.2516993133545 }, + } +`; + +export const validParsed = Yaml.safeLoad(validYaml).Authorities; + +export const invalidParsedNoBounds = Yaml.safeLoad(invalidYamlWithoutBounds) + .Authorities; + +export const invalidParsedNoUrl = Yaml.safeLoad(invalidYamlWithoutUrl) + .Authorities; diff --git a/app/services/__mocks__/mockUserLocHistory.js b/app/services/__mocks__/mockUserLocHistory.js new file mode 100644 index 0000000000..7e0e18e01b --- /dev/null +++ b/app/services/__mocks__/mockUserLocHistory.js @@ -0,0 +1,25 @@ +export const mockNullUserLocHistory = [ + { + latitude: 0, + longitude: 0, + time: 0, + }, +]; + +export const mockUserLocHistory = [ + { + latitude: 38.421998333333335, + longitude: -123.08400000000002, + time: 1586714348000, + }, + { + latitude: 37.421998333333335, + longitude: -122.08400000000002, + time: 1586145111983, + }, +]; + +export const mockNullMostRecentUserLoc = mockNullUserLocHistory[0]; + +export const mockMostRecentUserLoc = + mockUserLocHistory[mockUserLocHistory.length - 1]; diff --git a/app/services/__tests__/HCAService.spec.js b/app/services/__tests__/HCAService.spec.js new file mode 100644 index 0000000000..de8bf39e78 --- /dev/null +++ b/app/services/__tests__/HCAService.spec.js @@ -0,0 +1,306 @@ +import { + AUTHORITY_SOURCE_SETTINGS, + ENABLE_HCA_AUTO_SUBSCRIPTION, +} from '../../constants/storage'; +import * as storageHelpers from '../../helpers/General'; +// eslint-disable-next-line jest/no-mocks-import +import * as mockHCA from '../__mocks__/mockHCA'; +// eslint-disable-next-line jest/no-mocks-import +import { + mockMostRecentUserLoc, + mockNullMostRecentUserLoc, + mockNullUserLocHistory, + mockUserLocHistory, +} from '../__mocks__/mockUserLocHistory'; +import { HCAService } from '../HCAService'; +import { LocationData } from '../LocationService'; + +jest.mock('rn-fetch-blob', () => { + return { + fs: { + readFile: () => mockHCA.validYaml, + }, + }; +}); + +describe('HCAService', () => { + describe('getAuthoritiesList()', () => { + it('Given a successful reseponse, returns a list of health care authorities with a valid schema', async () => { + jest + .spyOn(HCAService, 'fetchAuthoritiesYaml') + .mockResolvedValueOnce({ path: () => '' }); + + const authorities = await HCAService.getAuthoritiesList(); + const testAuthority = authorities[0]['Test Authority']; + + expect(testAuthority[0]['url']).toBe( + 'https://raw.githack.com/tripleblindmarket/safe-places/develop/examples/safe-paths.json', + ); + + // Has a `bounds` with a `ne` and `sw` field + expect(testAuthority[1]['bounds']['ne']).toBeTruthy(); + expect(testAuthority[1]['bounds']['sw']).toBeTruthy(); + }); + + it('Given an unsuccessful response, returns an empty array', async () => { + console.error = jest.fn(); // Supress error log + + jest + .spyOn(HCAService, 'fetchAuthoritiesYaml') + .mockImplementationOnce(() => { + throw new Error(); + }); + + await expect(HCAService.getAuthoritiesList()).resolves.toEqual([]); + }); + }); + + describe('hasSavedAuthorities()', () => { + it('returns false when the user has not saved any health care authorities yet', async () => { + jest + .spyOn(HCAService, 'getUserAuthorityList') + .mockResolvedValueOnce(false); + await expect(HCAService.hasSavedAuthorities()).resolves.toBe(false); + }); + + it('returns true when the user has saved a health care authority', async () => { + jest + .spyOn(HCAService, 'getUserAuthorityList') + .mockResolvedValueOnce(mockHCA.validParsed); + + await expect(HCAService.hasSavedAuthorities()).resolves.toBe(true); + }); + }); + + describe('getAuthoritiesFromUserLocHistory()', () => { + beforeEach(() => { + jest + .spyOn(HCAService, 'getAuthoritiesList') + .mockResolvedValue(mockHCA.validParsed); + }); + + it('returns true if the user was in the bounding box of an authority in the past 28 days', async () => { + // Returns a point within the bounding box of an authority + jest + .spyOn(LocationData.prototype, 'getLocationData') + .mockReturnValueOnce(mockUserLocHistory); + + const authorities = await HCAService.getAuthoritiesFromUserLocHistory(); + expect(authorities[0]).toEqual(mockHCA.validParsed[0]); + }); + + it('returns false if the user was not in the bounding box of any authority in the past 28 days', async () => { + // Returns a point outside the bounding box of any authority + jest + .spyOn(LocationData.prototype, 'getLocationData') + .mockReturnValueOnce(mockNullUserLocHistory); + + await expect( + HCAService.getAuthoritiesFromUserLocHistory(), + ).resolves.toEqual([]); + }); + }); + + describe('findNewAuthorities()', () => { + let autoSubscribeAlertSpy, newAuthoritiesAlertSpy; + + beforeEach(() => { + autoSubscribeAlertSpy = jest.spyOn( + HCAService, + 'pushAlertNewSubscriptions', + ); + + newAuthoritiesAlertSpy = jest.spyOn( + HCAService, + 'pushAlertNewSubscriptions', + ); + }); + + afterEach(() => { + autoSubscribeAlertSpy.mockClear(); + newAuthoritiesAlertSpy.mockClear(); + }); + + describe('when there are new authorities', () => { + beforeEach(() => { + jest + .spyOn(HCAService, 'getNewAuthoritiesInUserLoc') + .mockResolvedValueOnce(mockHCA.validParsed); + }); + + it('when auto subscribed, alerts the user to their new authorities', async () => { + jest + .spyOn(HCAService, 'isAutosubscriptionEnabled') + .mockResolvedValueOnce(true); + await HCAService.findNewAuthorities(); + expect(autoSubscribeAlertSpy).toHaveBeenCalledTimes(1); + }); + + it('when not auto subscribed, alerts the user to new authorities available for subscription', async () => { + jest + .spyOn(HCAService, 'isAutosubscriptionEnabled') + .mockResolvedValueOnce(false); + await HCAService.findNewAuthorities(); + expect(newAuthoritiesAlertSpy).toHaveBeenCalledTimes(1); + }); + }); + + it('does not prompt the user there are no new authorities', async () => { + jest + .spyOn(HCAService, 'getNewAuthoritiesInUserLoc') + .mockResolvedValueOnce([]); + await HCAService.findNewAuthorities(); + expect(autoSubscribeAlertSpy).toHaveBeenCalledTimes(0); + expect(newAuthoritiesAlertSpy).toHaveBeenCalledTimes(0); + }); + }); + + describe('getAuthorityBounds()', () => { + it('returns undefined if the authority does not have a `bounds` key', () => { + expect( + HCAService.getAuthorityBounds(mockHCA.invalidParsedNoBounds), + ).toEqual(undefined); + }); + + it('returns the `bounds` key that is in the second index of authority values array', () => { + const bounds = { + ne: { latitude: 36.42025904738132, longitude: -121.93670068664551 }, + sw: { latitude: 38.29988330010084, longitude: -123.2516993133545 }, + }; + expect(HCAService.getAuthorityBounds(mockHCA.validParsed[0])).toEqual( + bounds, + ); + }); + }); + + describe('getAuthorityUrl()', () => { + it('returns undefined if the authority does not have a `url` key', () => { + expect(HCAService.getAuthorityUrl(mockHCA.invalidParsedNoBounds)).toEqual( + undefined, + ); + }); + + it('returns the `url` key that is in the first index of authority values array', () => { + const url = + 'https://raw.githack.com/tripleblindmarket/safe-places/develop/examples/safe-paths.json'; + expect(HCAService.getAuthorityUrl(mockHCA.validParsed[0])).toEqual(url); + }); + }); + + describe('getNewAuthoritiesInUserLoc()', () => { + beforeEach(() => { + jest + .spyOn(HCAService, 'getAuthoritiesList') + .mockResolvedValue(mockHCA.validParsed); + }); + + it('returns an empty array if there are no authorities in the area', async () => { + jest + .spyOn(LocationData.prototype, 'getMostRecentUserLoc') + .mockReturnValueOnce(mockNullMostRecentUserLoc); + jest.spyOn(HCAService, 'getUserAuthorityList').mockResolvedValueOnce([]); + + await expect(HCAService.getNewAuthoritiesInUserLoc()).resolves.toEqual( + [], + ); + }); + + it('filters out authorities the user has already subscribed to', async () => { + jest + .spyOn(LocationData.prototype, 'getMostRecentUserLoc') + .mockReturnValueOnce(mockMostRecentUserLoc); + jest + .spyOn(HCAService, 'getUserAuthorityList') + .mockResolvedValueOnce(mockHCA.validParsed); + + await expect(HCAService.getNewAuthoritiesInUserLoc()).resolves.toEqual( + [], + ); + }); + + it('returns an array of new authorities that the user has not subscribed to and are within their current location', async () => { + jest + .spyOn(LocationData.prototype, 'getMostRecentUserLoc') + .mockReturnValueOnce(mockMostRecentUserLoc); + jest.spyOn(HCAService, 'getUserAuthorityList').mockResolvedValueOnce([]); + + await expect(HCAService.getNewAuthoritiesInUserLoc()).resolves.toEqual( + mockHCA.validParsed, + ); + }); + }); + + describe('hasUserSetSubscription()', () => { + it('returns false if the user has not set a subcription status yet', async () => { + await expect(HCAService.hasUserSetSubscription()).resolves.toBe(false); + }); + + it('returns true if the user has not set a subcription status - either true or false', async () => { + await storageHelpers.SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, true); + await expect(HCAService.hasUserSetSubscription()).resolves.toBe(true); + + await storageHelpers.SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, false); + await expect(HCAService.hasUserSetSubscription()).resolves.toBe(true); + }); + }); + + describe('isAutosubscriptionEnabled()', () => { + it('returns false if the user has not enabled auto subscription', async () => { + await storageHelpers.SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, false); + await expect(HCAService.isAutosubscriptionEnabled()).resolves.toBe(false); + }); + + it('returns true if the user has enabled auto subscription', async () => { + await storageHelpers.SetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION, true); + await expect(HCAService.isAutosubscriptionEnabled()).resolves.toBe(true); + }); + }); + + describe('enableAutoSubscription()', () => { + it('sets `ENABLE_HCA_AUTO_SUBSCRIPTION` to "true" in storage', async () => { + await HCAService.enableAutoSubscription(); + await expect( + storageHelpers.GetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION), + ).resolves.toBe('true'); + }); + }); + + describe('disableAutoSubscription()', () => { + it('sets `ENABLE_HCA_AUTO_SUBSCRIPTION` to "false" in storage', async () => { + await HCAService.disableAutoSubscription(); + await expect( + storageHelpers.GetStoreData(ENABLE_HCA_AUTO_SUBSCRIPTION), + ).resolves.toBe('false'); + }); + }); + + describe('getUserAuthorityList()', () => { + it('returns the list of Health Care Authorities that a user has saved', async () => { + await storageHelpers.SetStoreData( + AUTHORITY_SOURCE_SETTINGS, + mockHCA.validParsed, + ); + + await expect(HCAService.getUserAuthorityList()).resolves.toEqual( + mockHCA.validParsed, + ); + }); + }); + + describe('appendToAuthorityList()', () => { + it('gets the existing authority list, appends the new authorities, and saves the new list back to storage', async () => { + jest + .spyOn(HCAService, 'getUserAuthorityList') + .mockResolvedValueOnce(mockHCA.validParsed); + + await HCAService.appendToAuthorityList(mockHCA.validParsed); + + const newAuthorityList = await HCAService.getUserAuthorityList(); + + expect(newAuthorityList.length).toBe(mockHCA.validParsed.length + 1); + + // Doesn't overwrite existing elements + expect(newAuthorityList[0]).toEqual(mockHCA.validParsed[0]); + }); + }); +}); diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index d61d6fc2b9..ce0b1c5191 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -1,12 +1,11 @@ -import Yaml from 'js-yaml'; import React, { Component } from 'react'; import { Alert, BackHandler, - Dimensions, FlatList, Image, StyleSheet, + Switch, TouchableOpacity, View, } from 'react-native'; @@ -18,27 +17,22 @@ import { renderers, withMenuContext, } from 'react-native-popup-menu'; -import RNFetchBlob from 'rn-fetch-blob'; -import backArrow from './../assets/images/backArrow.png'; import closeIcon from './../assets/images/closeIcon.png'; import saveIcon from './../assets/images/saveIcon.png'; +import { Checkbox } from '../components/Checkbox'; import { DynamicTextInput } from '../components/DynamicTextInput'; import NavigationBarWrapper from '../components/NavigationBarWrapper'; import { Typography } from '../components/Typography'; -import { AUTHORITIES_LIST_URL } from '../constants/authorities'; -import colors from '../constants/colors'; import Colors from '../constants/colors'; -import fontFamily from '../constants/fonts'; import { AUTHORITY_SOURCE_SETTINGS, LAST_CHECKED } from '../constants/storage'; -import { GetStoreData, SetStoreData } from '../helpers/General'; +import { SetStoreData } from '../helpers/General'; import { checkIntersect } from '../helpers/Intersect'; import languages from '../locales/languages'; +import { HCAService } from '../services/HCAService'; const { SlideInMenu } = renderers; -const width = Dimensions.get('window').width; - class ChooseProviderScreen extends Component { constructor(props) { super(props); @@ -48,6 +42,8 @@ class ChooseProviderScreen extends Component { urlEntryInProgress: false, urlText: '', authoritiesList: [], + isAuthorityFilterActive: true, + isAutoSubscribed: false, }; } @@ -60,22 +56,11 @@ class ChooseProviderScreen extends Component { return true; }; - componentDidMount() { + async componentDidMount() { BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); - this.fetchAuthoritiesList(); - - // Update user settings state from async storage - GetStoreData(AUTHORITY_SOURCE_SETTINGS, false).then(result => { - if (result !== null) { - console.log('Retrieving settings from async storage:'); - console.log(result); - this.setState({ - selectedAuthorities: result, - }); - } else { - console.log('No stored authority settings.'); - } - }); + await this.fetchAuthoritiesList(this.state.isAuthorityFilterActive); + await this.fetchUserAuthorities(); + await this.fetchAutoSubcribeStatus(); } componentWillUnmount() { @@ -87,38 +72,37 @@ class ChooseProviderScreen extends Component { checkIntersect(); } - fetchAuthoritiesList() { - try { - RNFetchBlob.config({ - // add this option that makes response data to be stored as a file, - // this is much more performant. - fileCache: true, - }) - .fetch('GET', AUTHORITIES_LIST_URL, { - //some headers .. - }) - .then(result => { - RNFetchBlob.fs.readFile(result.path(), 'utf8').then(list => { - // If unable to load the file, change state to display error in appropriate menu - let parsedFile = Yaml.safeLoad(list).Authorities; - { - parsedFile !== undefined - ? this.setState({ - authoritiesList: parsedFile, - }) - : this.setState({ - authoritiesList: [ - { - 'Unable to load authorities list': [{ url: 'No URL' }], // TODO: Localize - }, - ], - }); - } - }); - }); - } catch (error) { - console.log(error); + async fetchUserAuthorities() { + const selectedAuthorities = await HCAService.getUserAuthorityList(); + + if (selectedAuthorities) { + this.setState({ selectedAuthorities }); + } else { + console.log('No stored authority settings.'); + } + } + + async fetchAutoSubcribeStatus() { + const isAutoSubscribed = await HCAService.isAutosubscriptionEnabled(); + this.setState({ isAutoSubscribed }); + } + + /** + * + * @param {boolean} filterByGPSHistory - used to filter the list of HCAs based on the + * 28 day location history of the user + * @returns void + */ + async fetchAuthoritiesList(filterByGPSHistory) { + let authoritiesList = []; + + if (filterByGPSHistory) { + authoritiesList = await HCAService.getAuthoritiesFromUserLocHistory(); + } else { + authoritiesList = await HCAService.getAuthoritiesList(); } + + this.setState({ authoritiesList }); } // Add selected authorities to state, for display in the FlatList @@ -215,33 +199,57 @@ class ChooseProviderScreen extends Component { ); } + toggleFilterAuthoritesByGPSHistory() { + this.filterAuthoritesByGPSHistory({ + val: !this.state.isAuthorityFilterActive, + }); + } + + async filterAuthoritesByGPSHistory(isAuthorityFilterActive) { + await this.fetchAuthoritiesList(isAuthorityFilterActive.val); + this.setState({ isAuthorityFilterActive: isAuthorityFilterActive.val }); + } + + async toggleAutoSubscribe() { + this.setState( + prevState => ({ + isAutoSubscribed: !prevState.isAutoSubscribed, + }), + async () => { + this.state.isAutoSubscribed + ? await HCAService.enableAutoSubscription() + : await HCAService.disableAutoSubscription(); + }, + ); + } + render() { return ( - + {languages.t('label.authorities_title')} - + {languages.t('label.authorities_desc')} + + this.toggleAutoSubscribe()} + /> + {Object.keys(this.state.selectedAuthorities).length == 0 ? ( <> + style={[styles.sectionDescription, styles.noDataSourceText]} + use={'headline2'}> {languages.t('label.authorities_no_sources')} this.addCustomUrlToState(this.state.urlText) } @@ -301,7 +309,9 @@ class ChooseProviderScreen extends Component { data={this.state.selectedAuthorities} renderItem={({ item }) => ( - {item.key} + + {item.key} + this.removeAuthorityFromState(item)}> @@ -324,12 +334,28 @@ class ChooseProviderScreen extends Component { this.props.ctx.menuActions.openMenu('AuthoritiesMenu') } disabled={this.state.urlEditInProgress}> - + {languages.t('label.authorities_add_button_label')} + this.toggleFilterAuthoritesByGPSHistory()}> + + {languages.t('label.filter_authorities_by_gps_history')} + + + this.filterAuthoritesByGPSHistory({ val }) + } + value={this.state.isAuthorityFilterActive} + /> + {this.state.authoritiesList === undefined ? null : this.state.authoritiesList.map(item => { @@ -343,7 +369,7 @@ class ChooseProviderScreen extends Component { this.addAuthorityToState(name); }} disabled={this.state.authoritiesList.length === 1}> - + {name} @@ -356,7 +382,7 @@ class ChooseProviderScreen extends Component { urlEntryInProgress: true, }); }}> - + {languages.t('label.authorities_add_url')} @@ -368,14 +394,6 @@ class ChooseProviderScreen extends Component { } const styles = StyleSheet.create({ - // Container covers the entire screen - container: { - flex: 1, - flexDirection: 'column', - justifyContent: 'space-between', - color: colors.PRIMARY_TEXT, - backgroundColor: colors.WHITE, - }, main: { flex: 2, flexDirection: 'column', @@ -393,16 +411,7 @@ const styles = StyleSheet.create({ padding: 20, width: '96%', alignSelf: 'center', - }, - row: { - flex: 1, - flexDirection: 'row', - color: colors.PRIMARY_TEXT, - backgroundColor: colors.WHITE, - }, - value: { - fontSize: 20, - fontWeight: '200', + backgroundColor: Colors.WHITE, }, startLoggingButtonTouchable: { borderRadius: 12, @@ -413,51 +422,31 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, startLoggingButtonText: { - fontFamily: fontFamily.primaryBold, - fontSize: 14, - lineHeight: 19, - letterSpacing: 0, - textAlign: 'center', - color: '#ffffff', - }, - - buttonTouchable: { - borderRadius: 12, - backgroundColor: '#665eff', - height: 52, - alignSelf: 'center', - width: width * 0.7866, - marginTop: 30, - justifyContent: 'center', - }, - buttonText: { - fontFamily: fontFamily.primaryBold, - fontSize: 14, - lineHeight: 19, - letterSpacing: 0, textAlign: 'center', color: '#ffffff', }, headerTitle: { - fontSize: 24, - fontFamily: fontFamily.primaryBold, color: Colors.VIOLET_TEXT, }, - backArrow: { - height: 18, - width: 18.48, - }, sectionDescription: { - fontSize: 16, - lineHeight: 22, marginTop: 12, overflow: 'scroll', color: Colors.VIOLET_TEXT, - fontFamily: fontFamily.primaryRegular, + }, + authorityFilter: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 5, + backgroundColor: Colors.LIGHT_GRAY, + borderTopWidth: 3, + borderTopColor: Colors.DIVIDER, + justifyContent: 'space-between', + }, + authorityFilterText: { + padding: 10, + color: Colors.VIOLET_TEXT, }, menuOptionText: { - fontFamily: fontFamily.primaryRegular, - fontSize: 14, padding: 10, }, flatlistRowView: { @@ -469,8 +458,6 @@ const styles = StyleSheet.create({ borderColor: '#999999', }, item: { - fontFamily: fontFamily.primaryRegular, - fontSize: 16, padding: 10, maxWidth: '90%', }, @@ -489,6 +476,13 @@ const styles = StyleSheet.create({ textInput: { marginLeft: 10, }, + autoSubcribe: { + paddingTop: 25, + }, + noDataSourceText: { + textAlign: 'center', + paddingTop: 30, + }, }); export default withMenuContext(ChooseProviderScreen); diff --git a/app/views/LocationTracking.js b/app/views/LocationTracking.js index 1667a8f1da..bb0a458e07 100644 --- a/app/views/LocationTracking.js +++ b/app/views/LocationTracking.js @@ -193,16 +193,6 @@ class LocationTracking extends Component { .catch(error => console.log(error)); } - findNewAuthorities() { - // TODO: This should pull down the Healtcare Authorities list (see Settings.js) - // Then it should look at the GPS extent box of each authority and (if any - // of the GPS coordinates change) pop-up a notification that is basically: - // There is a new "Healthcare Authority" for an area where you have - // been. - // Tapping that notification asks if they want to Add that Healthcare Authority - // under the Settings screen. - } - componentWillUnmount() { AppState.removeEventListener('change', this.handleAppStateChange); clearInterval(this.state.timer_intersect); @@ -261,31 +251,6 @@ class LocationTracking extends Component { }); }; - news() { - this.props.navigation.navigate('NewsScreen', {}); - } - - licenses() { - this.props.navigation.navigate('LicensesScreen', {}); - } - - settings() { - this.props.navigation.navigate('SettingsScreen', {}); - } - - notifications() { - this.props.navigation.navigate('NotificationScreen', {}); - } - - setOptOut = () => { - LocationServices.stop(this.props.navigation); - // Turn of bluetooth for v1 - //BroadcastingServices.stop(this.props.navigation); - this.setState({ - isLogging: false, - }); - }; - getBackground() { if (this.state.currentState === StateEnum.AT_RISK) { return BackgroundImageAtRisk; @@ -293,6 +258,10 @@ class LocationTracking extends Component { return BackgroundImage; } + settings() { + this.props.navigation.navigate('SettingsScreen', {}); + } + getSettings() { return ( - Choose health authority + Choose Health Authority - To be informed of exposures you will need to subscribe to a health authority. + To be informed of exposures you will need to subscribe to a Health Authority. diff --git a/app/views/onboarding/Onboarding5.js b/app/views/onboarding/Onboarding5.js index 4ae54570bb..a6b17c83c7 100644 --- a/app/views/onboarding/Onboarding5.js +++ b/app/views/onboarding/Onboarding5.js @@ -4,6 +4,7 @@ import { ImageBackground, StatusBar, StyleSheet, + TouchableOpacity, View, } from 'react-native'; import { @@ -17,17 +18,17 @@ import { import { SvgXml } from 'react-native-svg'; import BackgroundImage from './../../assets/images/launchScreenBackground.png'; +import { isPlatformiOS } from './../../Util'; import IconDenied from '../../assets/svgs/permissionDenied'; import IconGranted from '../../assets/svgs/permissionGranted'; import IconUnknown from '../../assets/svgs/permissionUnknown'; import ButtonWrapper from '../../components/ButtonWrapper'; -import { Type, Typography } from '../../components/Typography'; +import { Typography } from '../../components/Typography'; import Colors from '../../constants/colors'; -import fontFamily from '../../constants/fonts'; import { PARTICIPATE } from '../../constants/storage'; import { SetStoreData } from '../../helpers/General'; import languages from '../../locales/languages'; -import { isPlatformiOS } from '../../Util'; +import { HCAService } from '../../services/HCAService'; const width = Dimensions.get('window').width; @@ -37,6 +38,13 @@ const PermissionStatusEnum = { DENIED: 2, }; +const StepEnum = { + LOCATION: 0, + NOTIFICATIONS: 1, + HCA_SUBSCRIPTION: 2, + DONE: 3, +}; + const PermissionDescription = ({ title, status }) => { let icon; switch (status) { @@ -50,9 +58,12 @@ const PermissionDescription = ({ title, status }) => { icon = IconDenied; break; } + return ( - {title} + + {title} + ); @@ -62,11 +73,17 @@ class Onboarding extends Component { constructor(props) { super(props); this.state = { + currentStep: StepEnum.LOCATION, notificationPermission: PermissionStatusEnum.UNKNOWN, locationPermission: PermissionStatusEnum.UNKNOWN, + authSubscriptionStatus: PermissionStatusEnum.UNKNOWN, }; + } + + componentDidMount() { this.checkLocationStatus(); - this.checkNotificationStatus(); + isPlatformiOS() && this.checkNotificationStatus(); + this.checkSubsriptionStatus(); } isLocationChecked() { @@ -77,143 +94,256 @@ class Onboarding extends Component { return this.state.notificationPermission !== PermissionStatusEnum.UNKNOWN; } - checkLocationStatus() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + /** + * Helper method to determine the next step for permission requests. + * In general there is a linear flow, but because Android does not + * require permission for notifications, we skip the notifications + * step on Android. + * + * @param {currentStep} StepEnum + * @returns {StepEnum} + */ + getNextStep(currentStep) { + switch (currentStep) { + case StepEnum.LOCATION: + return isPlatformiOS() + ? StepEnum.NOTIFICATIONS + : StepEnum.HCA_SUBSCRIPTION; + case StepEnum.NOTIFICATIONS: + return StepEnum.HCA_SUBSCRIPTION; + case StepEnum.HCA_SUBSCRIPTION: + return StepEnum.DONE; + } + } + + async checkLocationStatus() { + const nextStep = this.getNextStep(StepEnum.LOCATION); + const setting = this.getLocationPermissionSetting(); + const status = await check(setting); + + switch (status) { + case RESULTS.GRANTED: + this.setState({ + currentStep: nextStep, + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.BLOCKED: + this.setState({ + currentStep: nextStep, + locationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + } + + async checkNotificationStatus() { + const nextStep = this.getNextStep(StepEnum.NOTIFICATIONS); + const { status } = await checkNotifications(); + + switch (status) { + case RESULTS.GRANTED: + this.setState({ + currentStep: nextStep, + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.BLOCKED: + this.setState({ + currentStep: nextStep, + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; } - check(locationPermission) - .then(result => { - switch (result) { - case RESULTS.GRANTED: - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }) - .catch(error => { - console.log('error checking location: ' + error); + } + + async checkSubsriptionStatus() { + const nextStep = this.getNextStep(StepEnum.HCA_SUBSCRIPTION); + const hasUserSetSubscription = await HCAService.hasUserSetSubscription(); + + // Only update state if the user has already set their subscription status + if (hasUserSetSubscription) { + const isEnabled = await HCAService.isAutosubscriptionEnabled(); + const authSubscriptionStatus = isEnabled + ? PermissionStatusEnum.GRANTED + : PermissionStatusEnum.DENIED; + + this.setState({ + currentStep: nextStep, + authSubscriptionStatus, }); + } } - checkNotificationStatus() { - checkNotifications().then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); + /** + * Gets the respective location permissions settings string + * for the user's current device. + * */ + getLocationPermissionSetting() { + return isPlatformiOS() + ? PERMISSIONS.IOS.LOCATION_ALWAYS + : PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; } - requestLocation() { - // NEED TO TEST ON ANNDROID - let locationPermission; - if (isPlatformiOS()) { - locationPermission = PERMISSIONS.IOS.LOCATION_ALWAYS; - } else { - locationPermission = PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION; + async requestLocation() { + const nextStep = this.getNextStep(StepEnum.LOCATION); + const locationPermission = this.getLocationPermissionSetting(); + const status = await request(locationPermission); + + switch (status) { + case RESULTS.GRANTED: + this.setState({ + currentStep: nextStep, + locationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.BLOCKED: + this.setState({ + currentStep: nextStep, + locationPermission: PermissionStatusEnum.DENIED, + }); + break; } - request(locationPermission).then(result => { - switch (result) { - case RESULTS.GRANTED: - console.log('Location granted'); - this.setState({ - locationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - locationPermission: PermissionStatusEnum.DENIED, - }); - break; - } - }); } - requestNotification() { - requestNotifications(['alert', 'badge', 'sound']).then(({ status }) => { - switch (status) { - case RESULTS.GRANTED: - this.setState({ - notificationPermission: PermissionStatusEnum.GRANTED, - }); - break; - case RESULTS.UNAVAILABLE: - case RESULTS.BLOCKED: - this.setState({ - notificationPermission: PermissionStatusEnum.DENIED, - }); - break; - } + async requestNotification() { + const nextStep = this.getNextStep(StepEnum.NOTIFICATIONS); + const { status } = await requestNotifications(['alert', 'badge', 'sound']); + + switch (status) { + case RESULTS.GRANTED: + this.setState({ + currentStep: nextStep, + notificationPermission: PermissionStatusEnum.GRANTED, + }); + break; + case RESULTS.BLOCKED: + this.setState({ + currentStep: nextStep, + notificationPermission: PermissionStatusEnum.DENIED, + }); + break; + } + } + + async requestHCASubscription() { + const nextStep = this.getNextStep(StepEnum.HCA_SUBSCRIPTION); + await HCAService.enableAutoSubscription(); + + this.setState({ + currentStep: nextStep, + authSubscriptionStatus: PermissionStatusEnum.GRANTED, }); } - buttonPressed() { - if (!this.isLocationChecked()) { - this.requestLocation(); - } else if (!this.isNotificationChecked()) { - this.requestNotification(); - } else { - SetStoreData(PARTICIPATE, 'true'); // replaces "start" button - SetStoreData('ONBOARDING_DONE', true); - this.props.navigation.replace('LocationTrackingScreen'); + /** + * Allows the user to skip over a given step by setting the + * permission for that step to `DENIED` + * @returns {StepEnum} + */ + skipCurrentStep() { + const status = PermissionStatusEnum.DENIED; + const nextStep = this.getNextStep(this.state.currentStep); + + switch (this.state.currentStep) { + case StepEnum.LOCATION: + this.setState({ + currentStep: nextStep, + locationPermission: status, + }); + break; + case StepEnum.NOTIFICATIONS: + this.setState({ + currentStep: nextStep, + notificationPermission: status, + }); + break; + case StepEnum.HCA_SUBSCRIPTION: + this.setState({ + currentStep: nextStep, + authSubscriptionStatus: status, + }); + break; + } + } + + async buttonPressed() { + switch (this.state.currentStep) { + case StepEnum.LOCATION: + this.requestLocation(); + break; + case StepEnum.NOTIFICATIONS: + this.requestNotification(); + break; + case StepEnum.HCA_SUBSCRIPTION: + this.requestHCASubscription(); + break; + case StepEnum.DONE: + SetStoreData(PARTICIPATE, 'true'); + SetStoreData('ONBOARDING_DONE', true); + this.props.navigation.replace('LocationTrackingScreen'); } } getTitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_header'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_header'); - } else { - return languages.t('label.launch_done_header'); + switch (this.state.currentStep) { + case StepEnum.LOCATION: + return languages.t('label.launch_location_header'); + case StepEnum.NOTIFICATIONS: + return languages.t('label.launch_notif_header'); + case StepEnum.HCA_SUBSCRIPTION: + return languages.t('label.launch_authority_header'); + case StepEnum.DONE: + return languages.t('label.launch_done_header'); } } getTitleTextView() { - if (!this.isLocationChecked() || !this.isNotificationChecked()) { - return ( - - {this.getTitleText()} - - ); - } else { - return ( - - {this.getTitleText()} - - ); - } + const use = + this.state.currentStep === StepEnum.DONE ? 'headline1' : 'headline2'; + + return ( + + {this.getTitleText()} + + ); } getSubtitleText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_location_subheader'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_notif_subheader'); - } else { - return languages.t('label.launch_done_subheader'); + let style, text; + + switch (this.state.currentStep) { + case StepEnum.LOCATION: + [style, text] = [ + styles.subheaderText, + languages.t('label.launch_location_subheader'), + ]; + break; + case StepEnum.NOTIFICATIONS: + [style, text] = [ + styles.subheaderText, + languages.t('label.launch_notif_subheader'), + ]; + break; + case StepEnum.HCA_SUBSCRIPTION: + [style, text] = [ + styles.subheaderTextWide, + languages.t('label.launch_authority_subheader'), + ]; + break; + case StepEnum.DONE: + [style, text] = [ + styles.subheaderText, + languages.t('label.launch_done_subheader'), + ]; + break; } + + return ( + + {text} + + ); } getLocationPermission() { @@ -230,8 +360,8 @@ class Onboarding extends Component { } getNotificationsPermissionIfIOS() { - if (isPlatformiOS()) { - return ( + return ( + isPlatformiOS() && ( <> - ); - } - return; + ) + ); + } + + getAuthSubscriptionStatus() { + return ( + <> + + + + ); } getButtonText() { - if (!this.isLocationChecked()) { - return languages.t('label.launch_enable_location'); - } else if (!this.isNotificationChecked()) { - return languages.t('label.launch_enable_notif'); - } else { - return languages.t('label.launch_finish_set_up'); + switch (this.state.currentStep) { + case StepEnum.LOCATION: + return languages.t('label.launch_enable_location'); + case StepEnum.NOTIFICATIONS: + return languages.t('label.launch_enable_notif'); + case StepEnum.HCA_SUBSCRIPTION: + return languages.t('label.launch_enable_auto_subscription'); + case StepEnum.DONE: + return languages.t('label.launch_finish_set_up'); + } + } + + getSkipStepButton() { + if (this.state.currentStep !== StepEnum.DONE) { + return ( + + + {languages.t('label.skip_this_step')} + + + ); } } @@ -266,12 +422,12 @@ class Onboarding extends Component { {this.getTitleTextView()} - - {this.getSubtitleText()} - + {this.getSubtitleText()} + {this.getSkipStepButton()} {this.getLocationPermission()} {this.getNotificationsPermissionIfIOS()} + {this.getAuthSubscriptionStatus()} @@ -305,22 +461,18 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignSelf: 'center', }, - bigHeaderText: { - color: Colors.WHITE, - lineHeight: 48.5, - paddingTop: 52 - 48.5, // lineHeight hack - width: width * 0.7, - }, headerText: { color: Colors.WHITE, - width: width * 0.8, }, subheaderText: { - marginTop: '3%', color: Colors.WHITE, - fontSize: 15, + marginTop: '3%', width: width * 0.55, - fontFamily: fontFamily.primaryRegular, + }, + subheaderTextWide: { + color: Colors.WHITE, + marginTop: '3%', + width: width * 0.8, }, statusContainer: { marginTop: '5%', @@ -345,13 +497,17 @@ const styles = StyleSheet.create({ }, permissionTitle: { color: Colors.WHITE, - fontSize: 16, alignSelf: 'center', - fontFamily: fontFamily.primaryRegular, + marginRight: 8, + flex: 1, }, permissionIcon: { alignSelf: 'center', }, + skipThisStepBtn: { + color: Colors.DIVIDER, + paddingTop: 15, + }, }); export default Onboarding; diff --git a/e2e/helpers/language.js b/e2e/helpers/language.js index 7c7019579f..f416997722 100644 --- a/e2e/helpers/language.js +++ b/e2e/helpers/language.js @@ -1,13 +1,12 @@ import * as english from '../../app/locales/en.json'; import * as haitian from '../../app/locales/ht.json'; -export const getLanguageStrings = localeName => { - switch (localeName) { - case 'en-US': - return english; - case 'ht-HT': - return haitian; - default: - return english; - } +const languageStrings = { + 'en-US': english, + 'ht-HT': haitian, }; + +export const languages = Object.keys(languageStrings).map(locale => [ + locale, + languageStrings[locale], +]); diff --git a/e2e/helpers/onboarding.js b/e2e/helpers/onboarding.js index 89ace6d6c9..afa2982ef5 100644 --- a/e2e/helpers/onboarding.js +++ b/e2e/helpers/onboarding.js @@ -1,11 +1,12 @@ +import EnableAuthoritySubscription from '../pages/EnableAuthoritySubscription.po.js'; +import FinishSetup from '../pages/FinishSetup.po.js'; import Onboarding1 from '../pages/Onboarding1.po.js'; import Onboarding2 from '../pages/Onboarding2.po.js'; import Onboarding3 from '../pages/Onboarding3.po.js'; import Onboarding4 from '../pages/Onboarding4.po.js'; -import Onboarding5 from '../pages/Onboarding5.po.js'; import SignEula from '../pages/SignEula.po.js'; -export const navigateThroughOnboarding = async languageStrings => { +export const navigateThroughPermissions = async languageStrings => { await Onboarding1.isOnScreen(languageStrings); await Onboarding1.tapButton(languageStrings); @@ -20,7 +21,23 @@ export const navigateThroughOnboarding = async languageStrings => { await Onboarding4.isOnScreen(languageStrings); await Onboarding4.tapButton(languageStrings); +}; + +export const navigateThroughOnboarding = async ( + languageStrings, + isAutoSubcribe = true, +) => { + await navigateThroughPermissions(languageStrings); + + await EnableAuthoritySubscription.isOnScreen(languageStrings); + + if (isAutoSubcribe) { + await EnableAuthoritySubscription.enable(languageStrings); + } else { + await EnableAuthoritySubscription.skipStep(languageStrings); + } - await Onboarding5.isOnScreen(languageStrings); - await Onboarding5.finishSetup(languageStrings); + await FinishSetup.isOnScreen(languageStrings); + await FinishSetup.takeScreenshot(languageStrings); + await FinishSetup.tapButton(languageStrings); }; diff --git a/e2e/onboarding/InsufficientLocationPermissions.spec.js b/e2e/onboarding/InsufficientLocationPermissions.spec.js index 5251022fff..8ce3f331fe 100644 --- a/e2e/onboarding/InsufficientLocationPermissions.spec.js +++ b/e2e/onboarding/InsufficientLocationPermissions.spec.js @@ -1,11 +1,10 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import { navigateThroughOnboarding } from '../helpers/onboarding'; import Home from '../pages/Home.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`Insufficient Permissions test suite in ${locale}`, () => { +describe.each(languages)( + `Insufficient Permissions test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -15,7 +14,6 @@ let languageStrings = {}; }, permissions: { location: 'inuse', notifications: 'YES' }, }); - languageStrings = getLanguageStrings(locale); }); describe('Location set to `inuse` and notifications `true` set', () => { @@ -33,5 +31,5 @@ let languageStrings = {}; await device.installApp(); }); }); - }); -}); + }, +); diff --git a/e2e/onboarding/NoAuthSubscriptionPermission.spec.js b/e2e/onboarding/NoAuthSubscriptionPermission.spec.js new file mode 100644 index 0000000000..35e117c196 --- /dev/null +++ b/e2e/onboarding/NoAuthSubscriptionPermission.spec.js @@ -0,0 +1,24 @@ +/* eslint-disable jest/expect-expect */ +import { languages } from '../helpers/language'; +import { navigateThroughOnboarding } from '../helpers/onboarding'; + +describe.each(languages)( + `Healthcare Authority auto subscription in %s`, + (locale, languageStrings) => { + beforeAll(async () => { + await device.launchApp({ + newInstance: true, + languageAndLocale: { + language: locale, + locale, + }, + permissions: { location: 'always', notifications: 'YES' }, + }); + }); + + it('Shows auto subscription as unset', async () => { + await navigateThroughOnboarding(languageStrings); + await device.takeScreenshot('No Auto Subscription'); + }); + }, +); diff --git a/e2e/onboarding/NoLocationPermissions.spec.js b/e2e/onboarding/NoLocationPermissions.spec.js index ca05225400..6e2e235d17 100644 --- a/e2e/onboarding/NoLocationPermissions.spec.js +++ b/e2e/onboarding/NoLocationPermissions.spec.js @@ -1,11 +1,10 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import { navigateThroughOnboarding } from '../helpers/onboarding'; import Home from '../pages/Home.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`No Location Permissions test suite in ${locale}`, () => { +describe.each(languages)( + `No Location Permissions test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -15,8 +14,8 @@ let languageStrings = {}; }, permissions: { location: 'never', notifications: 'YES' }, }); - languageStrings = getLanguageStrings(locale); }); + describe('Location set to `never` and notifications `true` set', () => { beforeAll(async () => { await navigateThroughOnboarding(languageStrings); @@ -32,5 +31,5 @@ let languageStrings = {}; await device.uninstallApp(); await device.installApp(); }); - }); -}); + }, +); diff --git a/e2e/onboarding/NoNotificationsPermissions.spec.js b/e2e/onboarding/NoNotificationsPermissions.spec.js index 02af75d919..d9b236e61f 100644 --- a/e2e/onboarding/NoNotificationsPermissions.spec.js +++ b/e2e/onboarding/NoNotificationsPermissions.spec.js @@ -1,11 +1,10 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import { navigateThroughOnboarding } from '../helpers/onboarding'; import Home from '../pages/Home.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`No Notifications test suite in ${locale}`, () => { +describe.each(languages)( + `No Notifications test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -15,7 +14,6 @@ let languageStrings = {}; }, permissions: { location: 'always', notifications: 'NO' }, }); - languageStrings = getLanguageStrings(locale); }); describe('Location set to `always` and notifications `false` set', () => { @@ -33,5 +31,5 @@ let languageStrings = {}; await device.uninstallApp(); await device.installApp(); }); - }); -}); + }, +); diff --git a/e2e/onboarding/Onboarding.spec.js b/e2e/onboarding/Onboarding.spec.js index 89cd932ed9..3459deaa8a 100644 --- a/e2e/onboarding/Onboarding.spec.js +++ b/e2e/onboarding/Onboarding.spec.js @@ -1,15 +1,13 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import Onboarding1 from '../pages/Onboarding1.po.js'; import Onboarding2 from '../pages/Onboarding2.po.js'; import Onboarding3 from '../pages/Onboarding3.po.js'; import Onboarding4 from '../pages/Onboarding4.po.js'; -import Onboarding5 from '../pages/Onboarding5.po.js'; import SignEula from '../pages/SignEula.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`Onboarding test suite in ${locale}`, () => { +describe.each(languages)( + `Onboarding test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -18,7 +16,6 @@ let languageStrings = {}; locale, }, }); - languageStrings = getLanguageStrings(locale); }); describe('Onboarding visual appearance', () => { @@ -42,12 +39,6 @@ let languageStrings = {}; await Onboarding4.isOnScreen(languageStrings); await Onboarding4.takeScreenshot(); await Onboarding4.tapButton(languageStrings); - - await Onboarding5.isOnScreen(languageStrings); - await Onboarding5.takeScreenshot(); - - await Onboarding5.enableLocation(languageStrings); - await Onboarding5.takeMenuScreenshot(); }); afterAll(async () => { @@ -55,5 +46,5 @@ let languageStrings = {}; await device.installApp(); }); }); - }); -}); + }, +); diff --git a/e2e/onboarding/Permissions.spec.js b/e2e/onboarding/Permissions.spec.js index 81789eefb7..f5a01bf06e 100644 --- a/e2e/onboarding/Permissions.spec.js +++ b/e2e/onboarding/Permissions.spec.js @@ -1,11 +1,10 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import { navigateThroughOnboarding } from '../helpers/onboarding'; import Home from '../pages/Home.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`Permissions test suite in ${locale}`, () => { +describe.each(languages)( + `Permissions test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -15,8 +14,8 @@ let languageStrings = {}; }, permissions: { location: 'always', notifications: 'YES' }, }); - languageStrings = getLanguageStrings(locale); }); + describe('Permissions: Location `always` and notifications `true` are chosen', () => { beforeAll(async () => { await navigateThroughOnboarding(languageStrings); @@ -32,5 +31,5 @@ let languageStrings = {}; await device.installApp(); }); }); - }); -}); + }, +); diff --git a/e2e/onboarding/UnsignedEula.spec.js b/e2e/onboarding/UnsignedEula.spec.js index 94710d9c7d..b33bfba83b 100644 --- a/e2e/onboarding/UnsignedEula.spec.js +++ b/e2e/onboarding/UnsignedEula.spec.js @@ -1,11 +1,10 @@ -import { getLanguageStrings } from '../helpers/language'; +import { languages } from '../helpers/language'; import Onboarding1 from '../pages/Onboarding1.po.js'; import SignEula from '../pages/SignEula.po.js'; -let languageStrings = {}; - -['en-US', 'ht-HT'].forEach(locale => { - describe(`No Notifications test suite in ${locale}`, () => { +describe.each(languages)( + `No Notifications test suite in %s`, + (locale, languageStrings) => { beforeAll(async () => { await device.launchApp({ newInstance: true, @@ -15,7 +14,6 @@ let languageStrings = {}; }, permissions: { location: 'always', notifications: 'YES' }, }); - languageStrings = getLanguageStrings(locale); }); describe('Cannot continue without signing the EULA', () => { @@ -32,5 +30,5 @@ let languageStrings = {}; await device.installApp(); }); }); - }); -}); + }, +); diff --git a/e2e/pages/EnableAuthoritySubscription.po.js b/e2e/pages/EnableAuthoritySubscription.po.js new file mode 100644 index 0000000000..bd83bfe439 --- /dev/null +++ b/e2e/pages/EnableAuthoritySubscription.po.js @@ -0,0 +1,24 @@ +class EnableAuthoritySubscription { + async enable(languageStrings) { + await element( + by.label(languageStrings.label.launch_enable_auto_subscription), + ).tap(); + } + + async skipStep(languageStrings) { + await element(by.text(languageStrings.label.skip_this_step).tap()); + } + + async takeScreenshot() { + await device.takeScreenshot('Authority Subscription Page'); + } + + async isOnScreen(languageStrings) { + // eslint-disable-next-line jest/no-standalone-expect + await expect( + element(by.text(languageStrings.label.launch_authority_header)), + ).toBeVisible(); + } +} + +export default new EnableAuthoritySubscription(); diff --git a/e2e/pages/EnableLocation.po.js b/e2e/pages/EnableLocation.po.js index 56b5afc054..8569057712 100644 --- a/e2e/pages/EnableLocation.po.js +++ b/e2e/pages/EnableLocation.po.js @@ -13,6 +13,13 @@ class EnableLocation { async takeMenuScreenshot() { await device.takeScreenshot(screenShotWithMenuText); } + + async isOnScreen(languageStrings) { + // eslint-disable-next-line jest/no-standalone-expect + await expect( + element(by.text(languageStrings.label.launch_location_header)), + ).toBeVisible(); + } } export default new EnableLocation(); diff --git a/e2e/pages/EnableNotifications.po.js b/e2e/pages/EnableNotifications.po.js new file mode 100644 index 0000000000..550edda5e1 --- /dev/null +++ b/e2e/pages/EnableNotifications.po.js @@ -0,0 +1,22 @@ +class EnableNotifications { + async tapButton(languageStrings) { + await element(by.label(languageStrings.label.launch_enable_notif)).tap(); + } + + async takeScreenshot() { + await device.takeScreenshot('Enable Notifications Page'); + } + + async takeMenuScreenshot() { + await device.takeScreenshot('Notifications Permissions Dialog'); + } + + async isOnScreen(languageStrings) { + // eslint-disable-next-line jest/no-standalone-expect + await expect( + element(by.text(languageStrings.label.launch_notif_header)), + ).toBeVisible(); + } +} + +export default new EnableNotifications(); diff --git a/e2e/pages/FinishSetup.po.js b/e2e/pages/FinishSetup.po.js index f5bed5d3b8..e010368e18 100644 --- a/e2e/pages/FinishSetup.po.js +++ b/e2e/pages/FinishSetup.po.js @@ -1,7 +1,7 @@ /* eslint-disable */ const screenshotText = 'Finish Setup Page'; -class EnableLocation { +class FinishSetup { async tapButton(languageStrings) { await element(by.label(languageStrings.label.launch_finish_set_up)).tap(); } @@ -18,4 +18,4 @@ class EnableLocation { } } -export default new EnableLocation(); +export default new FinishSetup(); diff --git a/e2e/pages/Onboarding5.po.js b/e2e/pages/Onboarding5.po.js deleted file mode 100644 index 7ecd377369..0000000000 --- a/e2e/pages/Onboarding5.po.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable */ -const screenshotText = 'Onboarding - Page 5'; -const screenShotWithMenuText = 'Location Permissions Dialog'; - -class Onboarding5 { - async finishSetup(languageStrings) { - await element(by.label(languageStrings.label.launch_finish_set_up)).tap(); - } - - async enableLocation(languageStrings) { - await element(by.label(languageStrings.label.launch_enable_location)).tap(); - } - - async enableNotification(languageStrings) { - await element(by.label(languageStrings.label.launch_enable_notif)).tap(); - } - - async takeScreenshot() { - await device.takeScreenshot(screenshotText); - } - - async takeMenuScreenshot() { - await device.takeScreenshot(screenShotWithMenuText); - } - - async isOnScreen(languageStrings) { - // eslint-disable-next-line jest/no-standalone-expect - await expect( - element(by.label(languageStrings.label.launch_location_access)), - ).toBeVisible(); - } -} - -export default new Onboarding5(); From 2e0456dea6d076f5e794b3629e72efdceb48859c Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 16:14:53 -0700 Subject: [PATCH 34/60] Fix "plural" keys, add i18n checks for new PRs (#648) --- .github/workflows/i18n.yml | 38 +++++-- app/components/NativePicker.js | 2 +- app/locales/check.sh | 12 +++ app/locales/en.json | 98 ++++++++++--------- app/views/Export.js | 34 +++---- app/views/Import.js | 16 +-- app/views/Settings.js | 4 +- app/views/Settings/GoogleMapsImport.js | 8 +- .../__snapshots__/Export.spec.js.snap | 8 +- .../__snapshots__/Import.spec.js.snap | 14 +-- i18next-parser.config.js | 24 +---- package.json | 1 + 12 files changed, 141 insertions(+), 118 deletions(-) create mode 100755 app/locales/check.sh diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 23f998a154..1fa5c6851e 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -1,23 +1,23 @@ name: i18n on: - # upload strings merged into develop push: branches: [develop] paths: - app/locales/en.json - ios/en.lproj/*.strings - android/app/src/main/res/values/strings.xml - # for testing - # pull_request: - # branches: [develop] - # paths: - # - app/locales/en.json - # - ios/en.lproj/*.strings - # - android/app/src/main/res/values/strings.xml + pull_request: + branches: [develop] + paths: + - app/locales/en.json + - ios/en.lproj/*.strings + - android/app/src/main/res/values/strings.xml jobs: + # Only run on a final merge into develop, uploads all changed keys to Lokalised lokalise-upload: + if: github.event_name == 'push' && github.ref == 'develop' runs-on: ubuntu-latest env: UPLOAD_FLAGS: --lang-iso=en --cleanup-mode --replace-modified --include-path --detect-icu-plurals --apply-tm --convert-placeholders --project-id=${{ secrets.LOKALISE_PROJECT_ID }} --token=${{ secrets.LOKALISE_TOKEN }} @@ -29,3 +29,25 @@ jobs: - name: upload English files run: ./bin/lokalise2 file upload --file=app/locales/en.json,ios/en.lproj/InfoPlist.strings,ios/en.lproj/Localizable.strings,android/app/src/main/res/values/strings.xml $UPLOAD_FLAGS + + # Run on PRs, checks for any added keys that are missing in the app/locales/en.json file + no-missing-keys: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - name: cached node_modules/ + uses: actions/cache@v1 + id: cache + with: + path: node_modules + key: ${{ runner.OS }}-yarn-cache-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.OS }}-yarn-cache- + + - run: yarn --frozen-lockfile + if: steps.cache.outputs.cache-hit != 'true' + + - run: yarn i18n:check + + diff --git a/app/components/NativePicker.js b/app/components/NativePicker.js index 3d6a1ad9d3..a12387701a 100644 --- a/app/components/NativePicker.js +++ b/app/components/NativePicker.js @@ -105,7 +105,7 @@ export default class NativePicker extends Component { marginRight: 22, }} onPress={() => this.setState({ modalVisible: false })}> - {languages.t('Done')} + {languages.t('common.done')} { } return ( - <> + { - - {t('label.tested_positive_title')} + + {t('share.title')} - - {t('label.export_para_1')} + + {t('share.paragraph_first')} - - {t('label.export_para_2')} + + {t('share.paragraph_second')} - {t('label.share_location_data')} + {t('share.button_text')} @@ -149,7 +150,7 @@ export const ExportScreen = ({ navigation }) => { - + ); }; @@ -184,24 +185,13 @@ const styles = StyleSheet.create({ flex: 1, paddingHorizontal: 26, }, - row: { - flexDirection: 'row', - color: Colors.PRIMARY_TEXT, - alignItems: 'flex-start', - }, - exportSectionTitles: { - color: Colors.WHITE, - fontSize: 26, - fontFamily: fontFamily.primaryMedium, marginTop: 9, + fontWeight: 'normal', + fontFamily: fontFamily.primaryMedium, }, exportSectionPara: { - color: Colors.WHITE, - fontSize: 18, - lineHeight: 22.5, marginTop: 22, - fontFamily: fontFamily.primaryRegular, }, exportButton: { diff --git a/app/views/Import.js b/app/views/Import.js index f28877d14c..c997d47706 100644 --- a/app/views/Import.js +++ b/app/views/Import.js @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Dimensions, Linking, @@ -28,6 +29,7 @@ const makeImportResults = (label = '', error = false) => ({ }); const ImportScreen = props => { + const { t } = useTranslation(); const { navigation: { goBack }, } = props; @@ -64,19 +66,21 @@ const ImportScreen = props => { return ( - {languages.t('label.import_step_1')} + {t('import.google.instructions_first')} + {/* eslint-disable react/no-unescaped-entities */} - {languages.t('label.import_step_2')} + {t('import.google.instructions_second')} - {languages.t('label.import_step_3')} + {t('import.google.instructions_detailed')} + {/* eslint-enable react/no-unescaped-entities */} @@ -86,7 +90,7 @@ const ImportScreen = props => { } style={styles.buttonTouchable}> - {languages.t('label.import_takeout').toUpperCase()} + {languages.t('import.google.visit_button_text')} { onPress={importPickFile} style={styles.buttonTouchable}> - {languages.t('label.import_title').toUpperCase()} + {languages.t('import.title')} diff --git a/app/views/Settings.js b/app/views/Settings.js index 77f86cfbaa..bfa29be41c 100644 --- a/app/views/Settings.js +++ b/app/views/Settings.js @@ -121,8 +121,8 @@ export const SettingsScreen = ({ navigation }) => { onPress={() => navigation.navigate('ExposureHistoryScreen')} /> navigation.navigate('ExportScreen')} last /> diff --git a/app/views/Settings/GoogleMapsImport.js b/app/views/Settings/GoogleMapsImport.js index ebd93e095e..c5caaf20a9 100644 --- a/app/views/Settings/GoogleMapsImport.js +++ b/app/views/Settings/GoogleMapsImport.js @@ -19,15 +19,15 @@ export const GoogleMapsImport = ({ navigation }) => { <> - {t('label.maps_import_title')} + {t('import.google.title')} - {t('label.maps_import_text')} + {t('import.subtitle')} { - {t('label.maps_import_disclaimer')} + {t('import.google.disclaimer')} diff --git a/app/views/__tests__/__snapshots__/Export.spec.js.snap b/app/views/__tests__/__snapshots__/Export.spec.js.snap index fcad2cf272..86d70023fc 100644 --- a/app/views/__tests__/__snapshots__/Export.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Export.spec.js.snap @@ -118,12 +118,12 @@ exports[` renders correctly 1`] = ` "fontFamily": "IBMPlexSans-Medium", "fontSize": 26, "fontWeight": "normal", - "lineHeight": 24, + "lineHeight": 34, "marginTop": 9, "writingDirection": "ltr", } } - use="body1" + use="headline2" > Share location history @@ -134,7 +134,7 @@ exports[` renders correctly 1`] = ` "fontFamily": "IBMPlexSans", "fontSize": 18, "fontWeight": "normal", - "lineHeight": 22.5, + "lineHeight": 24, "marginTop": 22, "writingDirection": "ltr", } @@ -150,7 +150,7 @@ exports[` renders correctly 1`] = ` "fontFamily": "IBMPlexSans", "fontSize": 18, "fontWeight": "normal", - "lineHeight": 22.5, + "lineHeight": 24, "marginTop": 22, "writingDirection": "ltr", } diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index cd5479399f..d16de84915 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -145,7 +145,7 @@ Array [ } use="body1" > - Before you can import, you must first 'Take out' your location data from Google. + Before you can import, you must first "Take out" your location data from Google. - Visit Google Takeout and export your Location History using following settings: + Visit Google Takeout and export your Location History using the following settings: 1. Delivery method: "Add to Drive" 2. Frequency: "Export once" -3. File type & size: ".zip" and "1GB" +3. File type & size: ".zip" and "1GB" 4. Google sends an email when the export is ready -5. Return here to import locations. Import options: -- Import from Google Drive +5. Return here to import locations. Import options: +- Import from Google Drive - Download from browser, then import from local phone files. Make sure to be on WiFi network as files can be big. - VISIT GOOGLE TAKEOUT + Visit Google Takeout - IMPORT LOCATIONS + Import Locations diff --git a/i18next-parser.config.js b/i18next-parser.config.js index 822130c2ea..83c7b9e218 100644 --- a/i18next-parser.config.js +++ b/i18next-parser.config.js @@ -2,7 +2,7 @@ module.exports = { contextSeparator: '_', // Key separator used in your translation keys - createOldCatalogs: true, + createOldCatalogs: false, // Save the \_old files defaultNamespace: 'default', @@ -38,24 +38,8 @@ module.exports = { lineEnding: 'auto', // Control the line ending. See options at https://github.com/ryanve/eol - locales: [ - // sort alphabetically - 'en', - 'es', - 'fr', - 'ht', - 'it', - 'id', - 'nl', - 'pl', - 'pt_BR', - 'ro', - 'ru', - 'sk', - 'vi', - 'zh-Hant', - ], - // An array of the locales in your applications + // Only en needs to be extracted and uploaded to lokalise. Lokalise will add/trim keys in other languages + locales: ['en'], namespaceSeparator: ':', // Namespace separator used in your translation keys @@ -81,6 +65,6 @@ module.exports = { // Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World" // The option `defaultValue` will not work if this is set to true - verbose: true, + verbose: false, // Display info about the parsing including some stats }; diff --git a/package.json b/package.json index 176b71ee38..bb720194e3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e:iphone-se": "detox test -c iphone-se.sim --loglevel=warn", "test:e2e:iphone8": "detox test -c iphone8.sim --loglevel=warn", "i18n:extract": "i18next", + "i18n:check": "./app/locales/check.sh", "test": "jest --config=./jest/config.js", "update-snapshots": "jest --config=./jest/config.js --updateSnapshot", "test:dev_setup": "bats __tests__/dev_setup.test.bats" From 6cebff5fb7b5734d94437371c8ee744d9469c4df Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 16:55:49 -0700 Subject: [PATCH 35/60] Run lokalize push on merge into develop (#698) --- .github/workflows/i18n.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 1fa5c6851e..d28438a2ca 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -17,7 +17,7 @@ on: jobs: # Only run on a final merge into develop, uploads all changed keys to Lokalised lokalise-upload: - if: github.event_name == 'push' && github.ref == 'develop' + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' runs-on: ubuntu-latest env: UPLOAD_FLAGS: --lang-iso=en --cleanup-mode --replace-modified --include-path --detect-icu-plurals --apply-tm --convert-placeholders --project-id=${{ secrets.LOKALISE_PROJECT_ID }} --token=${{ secrets.LOKALISE_TOKEN }} From fcde15af4bdec864af737dc1f2db241adf986b13 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 17:09:31 -0700 Subject: [PATCH 36/60] Really really push to lokalise (#699) --- .github/workflows/i18n.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index d28438a2ca..17f6171846 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -7,12 +7,14 @@ on: - app/locales/en.json - ios/en.lproj/*.strings - android/app/src/main/res/values/strings.xml + - .github/workflows/*.yml pull_request: branches: [develop] paths: - app/locales/en.json - ios/en.lproj/*.strings - android/app/src/main/res/values/strings.xml + - .github/workflows/*.yml jobs: # Only run on a final merge into develop, uploads all changed keys to Lokalised From 6c62ab543e0fded035679db0af4db9312883b1bc Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 17:34:14 -0700 Subject: [PATCH 37/60] Add a script to pull translations from lokalise (#641) --- .lokalise.yml | 4 + .../app/src/main/res/values-es/strings.xml | 5 + .../app/src/main/res/values-ht/strings.xml | 5 + .../app/src/main/res/values-it/strings.xml | 5 + .../app/src/main/res/values-ml/strings.xml | 5 + .../app/src/main/res/values-nl/strings.xml | 5 + android/app/src/main/res/values/strings.xml | 15 +- app/locales/es.json | 191 ++++++++-------- app/locales/fil.json | 53 +++++ app/locales/fr.json | 61 ++---- app/locales/ht.json | 61 ++---- app/locales/id.json | 64 +++--- app/locales/it.json | 51 +++-- app/locales/ja.json | 85 ++++++++ app/locales/languages.js | 2 +- app/locales/ml.json | 91 ++++---- app/locales/nl.json | 87 ++++---- app/locales/pt_BR.json | 39 ++-- app/locales/pull.sh | 65 ++++++ app/locales/ro.json | 30 +-- app/locales/ru.json | 204 ++++++++++-------- app/locales/sk.json | 192 ++++++++--------- app/locales/vi.json | 161 +++++++------- app/locales/{zh-Hant.json => zh_Hant.json} | 53 ++--- ios/en.lproj/InfoPlist.strings | 6 +- ios/en.lproj/Localizable.strings | 5 +- ios/es.lproj/InfoPlist.strings | 6 +- ios/es.lproj/Localizable.strings | 4 + ios/fr.lproj/Localizable.strings | 2 + ios/ht.lproj/InfoPlist.strings | 5 + ios/ht.lproj/Localizable.strings | 4 + ios/it.lproj/InfoPlist.strings | 5 + ios/it.lproj/Localizable.strings | 4 + ios/ml.lproj/InfoPlist.strings | 5 + ios/ml.lproj/Localizable.strings | 4 + ios/nl.lproj/InfoPlist.strings | 5 + ios/nl.lproj/Localizable.strings | 4 + ios/ru.lproj/InfoPlist.strings | 5 + ios/ru.lproj/Localizable.strings | 4 + ios/zz-ZZ.lproj/InfoPlist.strings | 5 + ios/zz-ZZ.lproj/Localizable.strings | 4 + package.json | 3 +- 42 files changed, 935 insertions(+), 679 deletions(-) create mode 100644 .lokalise.yml create mode 100644 android/app/src/main/res/values-es/strings.xml create mode 100644 android/app/src/main/res/values-ht/strings.xml create mode 100644 android/app/src/main/res/values-it/strings.xml create mode 100644 android/app/src/main/res/values-ml/strings.xml create mode 100644 android/app/src/main/res/values-nl/strings.xml create mode 100644 app/locales/fil.json create mode 100644 app/locales/ja.json create mode 100755 app/locales/pull.sh rename app/locales/{zh-Hant.json => zh_Hant.json} (64%) create mode 100644 ios/ht.lproj/InfoPlist.strings create mode 100644 ios/ht.lproj/Localizable.strings create mode 100644 ios/nl.lproj/InfoPlist.strings create mode 100644 ios/nl.lproj/Localizable.strings create mode 100644 ios/zz-ZZ.lproj/InfoPlist.strings create mode 100644 ios/zz-ZZ.lproj/Localizable.strings diff --git a/.lokalise.yml b/.lokalise.yml new file mode 100644 index 0000000000..6b9f09fd23 --- /dev/null +++ b/.lokalise.yml @@ -0,0 +1,4 @@ +project-id: 331553825e9baabce4a297.57936175 +# Generate a READ_ONLY token from lokalise.com under your user profile: +# https://app.lokalise.com/profile +# token: diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..298a395110 --- /dev/null +++ b/android/app/src/main/res/values-es/strings.xml @@ -0,0 +1,5 @@ + + + + Configurar notificaciones locales + diff --git a/android/app/src/main/res/values-ht/strings.xml b/android/app/src/main/res/values-ht/strings.xml new file mode 100644 index 0000000000..a325937864 --- /dev/null +++ b/android/app/src/main/res/values-ht/strings.xml @@ -0,0 +1,5 @@ + + + + Kenbe tout notifikasyon lokal yo + diff --git a/android/app/src/main/res/values-it/strings.xml b/android/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..d3091d899f --- /dev/null +++ b/android/app/src/main/res/values-it/strings.xml @@ -0,0 +1,5 @@ + + + + Gestisci tutte le notifiche locali. + diff --git a/android/app/src/main/res/values-ml/strings.xml b/android/app/src/main/res/values-ml/strings.xml new file mode 100644 index 0000000000..d11afbb98f --- /dev/null +++ b/android/app/src/main/res/values-ml/strings.xml @@ -0,0 +1,5 @@ + + + + എല്ലാ പ്രാദേശിക അറിയിപ്പുകളും കൈകാര്യം ചെയ്യുക + diff --git a/android/app/src/main/res/values-nl/strings.xml b/android/app/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..f6b4f5786d --- /dev/null +++ b/android/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,5 @@ + + + + Alle lokale meldingen afhandelen + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index db139a18bd..74bbec0e43 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,10 +1,9 @@ + - - COVID Safe Paths - - - Safe Paths - - - Handle all local notifications + + COVID Safe Paths + + Safe Paths + + Handle all local notifications diff --git a/app/locales/es.json b/app/locales/es.json index e040207255..155be0dd6c 100644 --- a/app/locales/es.json +++ b/app/locales/es.json @@ -1,99 +1,100 @@ { + "history": { + "no_exposure": "No ha habido contacto", + "possible_exposure": "Posible", + "possible_exposure_para": "Es posible que hayas estado en contacto o cerca de alguien que ha dado positivo en COVID-19.", + "what_does_this_mean": "¿Qué significa esto?", + "what_does_this_mean_para": "Según tu historial de ubicaciones, es posible que hayas estado en contacto o cerca de alguien con COVID-19. Esto no significa que te hayas infectado, pero sí que podrías estarlo.\n\nPara obtener más información sobre lo que debes hacer, puedes consultar el sitio web de Mayo Clinic.", + "what_if_no_symptoms": "¿Qué pasa si no tengo síntomas?", + "what_if_no_symptoms_para": "Si no tienes síntomas pero quieres hacerte la prueba, puedes ir al punto de pruebas más cercano. \n\nLas personas sin síntomas también pueden tener el COVID e infectar a otros. Respetar el distanciamiento social y evitar el contacto con grupos grandes o personas en riesgo (ancianos, personas con otros problemas médicos) es importante para controlar tanto tú riesgo como el de los demás." + }, + "import": { + "button_text": "Importar ubicaciones", + "google": { + "disclaimer": "Safe Paths no está afiliado con Google y nunca compartirá tus datos.", + "title": "Google Maps", + "visit_button_text": "Accede a Google Takeout" + }, + "subtitle": "Para saber si has estado con alguien con COVID-19 antes de descargar esta app, puedes importar tu historial personal de ubicaciones.", + "title": "Importar historial" + }, "label": { - "about_title":"Acerca de", - "authorities_add_button_label":"Añadir fuente de datos fiable", - "authorities_add_url":"Añadir organismo via web", - "authorities_desc":"Elige las autoridades sanitarias de tu zona para obtener datos sobre posibles contactos con el COVID. Selecciona un nombre de la lista global, o introduce la URL del sitio web de una autoridad sanitaria que haya usado Safe Paths.", - "authorities_input_placeholder":"Introduce la URL aquí", - "authorities_no_sources":"No hay fuentes de datos", - "authorities_removal_alert_cancel":"Cancelar", - "authorities_removal_alert_desc":"Quieres eliminar este organismo como fuente de datos?", - "authorities_removal_alert_proceed":"Adelante", - "authorities_removal_alert_title":"Eliminar organismo", - "authorities_title":"Fuentes de datos fiables", - "choose_provider_subtitle":"Para informarte de posibles contactos, debes suscribirte a una autoridad sanitaria. ", - "choose_provider_title":"Elegir autoridades sanitarias", - "commitment":"Compromiso", - "commitment_para":"Safe Paths registra de forma segura tu ubicación y encuentra posibles contactos con el COVID. Tus datos NUNCA salen de tu dispositivo sin tu consentimiento.", - "default_news_site_name":"Noticias de Safe Paths", - "event_history_subtitle":"Entiende tu exposición al COVID-19 según los datos publicados por las autoridades sanitarias.", - "event_history_title":"Historial de exposición", - "export_para_1":"Si has dado positivo en COVID-19, ayuda a las autoridades locales compartiendo tu historial de ubicaciones.", - "export_para_2":"Tu historial se envía como una simple lista de ubicaciones en el tiempo, sin revelar datos personales.", - "home_at_risk_header":"Puedes haberte expuesto al COVID", - "home_at_risk_subsubtext":"Esto no implica que te hayas infectado.", - "home_at_risk_subtext":"Según tu historial de ubicaciones, es posible que hayas estado en contacto o cerca de alguien con COVID-19.", - "home_enable_location":"Permitir acceso a tu ubicación", - "home_mayo_link_heading":"Más información del COVID-19", - "home_mayo_link_label":"de expertos de la Mayo Clinic", - "home_next_steps":"Más información", - "home_no_contact_header":"No ha habido contacto", - "home_no_contact_subtext":"Según los datos disponibles, no has estado cerca de nadie que haya dado positivo en COVID-19.", - "home_unknown_header":"Desconocido", - "home_unknown_subtext":"Si no permites a la app el acceso a tu ubicación no puedes saber si has estado en riesgo", - "import_step_1":"1. Inicia sesión en tu cuenta de Google y descarga tu historial de ubicaciones", - "import_step_2":"2. Después de la descarga, vuelve a abrir esta ventana. Tus datos se importarán de forma automática", - "import_title":"Importar historial", - "latest_news":"Últimas noticias", - "launch_done_header":"Ya estás listo", - "launch_done_subheader":"Preparados para comenzar. Recuerda que puedes cambiar tus preferencias más tarde.", - "launch_enable_location":"Permitir acceso", - "launch_enable_notif":"Permitir notificaciones", - "launch_finish_set_up":"Finalizar configuración", - "launch_get_started":"Iniciar", - "launch_location_access":"Acceder a tu ubicación", - "launch_location_header":"Para recordar donde has estado, tu teléfono móvil necesita registrar tu ubicación.", - "launch_location_subheader":"No te preocupes, estos datos nunca saldrán de tu móvil sin tu consentimiento expreso.", - "launch_next":"Siguiente", - "launch_notif_header":"Con las notificaciones, podrás saber si has estado cerca de alguien con COVID.", - "launch_notif_subheader":"No te molestaremos salvo para enviarte información sobre tu riesgo de exposición.", - "launch_notification_access":"No te molestaremos salvo para enviarte información sobre tu riesgo de exposición.", - "launch_screen1_header":"La vuelta a la normalidad comienza aquí.", - "launch_screen2_header":"Recibe notificaciones si has estado con alguien que después dé positivo en COVID-19.", - "launch_screen2_subheader":"La información es poder.", - "launch_screen3_header":"Si das positivo, puedes optar por compartir tus datos de forma anónima", - "launch_screen3_subheader":"Ayuda a proteger a tu entorno más cercano.", - "launch_screen4_header":"Compartir depende de ti. Tus datos solo se guardan en tu móvil.", - "launch_screen4_subheader":"Si has dado positivo, solo tú eliges si quieres compartirlos.", - "launch_set_up_phone":"Configurar mi dispositivo", - "legal_page_title":"Aviso legal", - "less_than_one_minute":"menos de 1 minuto", - "loading_public_data":"cargando datos…", - "location_disabled_message":"COVID Safe Paths necesita tu ubicación.", - "location_disabled_title":"Registro de ubicación desactivado", - "location_enabled_message":"COVID Safe Paths está registrando en tu dispositivo tus coordenadas GPS cada 5 minutos de forma segura.", - "location_enabled_title":"COVID Safe Paths habilitado", - "maps_import_button_text":"Importar ubicaciones", - "maps_import_disclaimer":"Safe Paths no está afiliado con Google y nunca compartirá tus datos.", - "maps_import_text":"Para saber si has estado con alguien con COVID-19 antes de descargar esta app, puedes importar tu historial personal de ubicaciones.", - "maps_import_title":"Google Maps", - "nCoV2019_url_info":"Más información sobre los datos de este mapa", - "news_subtitle":"Novedades sobre el COVID-19 de tus autoridades sanitarias y noticias en general.", - "news_title":"Últimas Noticias", - "no_data":"No hay datos", - "notification_2_weeks_ago":"hace 2 semanas", - "notification_data_not_available":"No hay datos sobre exposición.", - "notification_random_data_button":"Elegir autoridades sanitarias", - "notification_title":"Perfil de exposición - 2 semanas", - "notification_today":"hoy", - "notification_warning_text":"Suscríbete a tus autoridades sanitarias para recibir datos sobre posibles riesgos de forma regular.", - "notifications_exposure_format":"Hace {{daysAgo}} días estuviste cerca de alguien con COVID-19 durante {{exposureTime}} minutos.", - "notifications_exposure_format_today":"Hoy has estado cerca de alguien con COVID-19 durante {{exposureTime}} minutos.", - "notifications_exposure_format_yesterday":"Ayer estuviste cerca de alguien con COVID-19 durante {{exposureTime}} minutos.", - "notifications_no_exposure":"No te has expuesto al COVID-19 en las últimas 2 semanas.", - "overlap_found_button_label":"Datos públicos descargados", - "overlap_no_results_button_label":"Datos públicos descargados", - "overlap_para_1":"El rastro verde representa tu historial de ubicaciones\\n\\nLos círculos violetas son los datos públicos", - "overlap_title":"Comprobar coincidencias", - "push_at_risk_message":"Has estado cerca de alguien con COVID-19", - "push_at_risk_title":"Puedes estar en riesgo", - "settings_title":"Panel de control", - "share_location_data":"Comparte tus datos", - "show_overlap":"Haz clic para ver información pública", - "team":"Equipo", - "team_para":"El equipo se compone de un consorcio de epidemiólogos, ingenieros, analistas de datos, defensores de la privacidad digital, profesores y investigadores de instituciones reconocidas como MIT, Harvard, la Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young y Link Ventures.", - "terms_of_use":"Condiciones de uso", - "tested_positive_subtitle":"Puedes compartir tus datos con las autoridades sanitarias u otros agentes, o guardar una copia de seguridad", - "tested_positive_title":"Compartir ubicaciones" + "about_title": "Acerca de", + "authorities_add_button_label": "Añadir fuente de datos fiable", + "authorities_add_url": "Añadir organismo via web", + "authorities_desc": "Elige las autoridades sanitarias de tu zona para obtener datos sobre posibles contactos con el COVID. Selecciona un nombre de la lista global, o introduce la URL del sitio web de una autoridad sanitaria que haya usado Safe Paths.", + "authorities_input_placeholder": "Introduce la URL aquí", + "authorities_no_sources": "No hay fuentes de datos", + "authorities_removal_alert_cancel": "Cancelar", + "authorities_removal_alert_desc": "Quieres eliminar este organismo como fuente de datos?", + "authorities_removal_alert_proceed": "Adelante", + "authorities_removal_alert_title": "Eliminar organismo", + "authorities_title": "Fuentes de datos fiables", + "choose_provider_subtitle": "Para informarte de posibles contactos, debes suscribirte a una autoridad sanitaria.", + "choose_provider_title": "Elegir autoridades sanitarias", + "commitment": "Compromiso", + "commitment_para": "Safe Paths registra de forma segura tu ubicación y encuentra posibles contactos con el COVID. Tus datos NUNCA salen de tu dispositivo sin tu consentimiento.", + "default_news_site_name": "Noticias de Safe Paths", + "event_history_subtitle": "Entiende tu exposición al COVID-19 según los datos publicados por las autoridades sanitarias.", + "event_history_title": "Historial de exposición", + "home_at_risk_header": "Puedes haberte expuesto al COVID", + "home_at_risk_subsubtext": "Esto no implica que te hayas infectado.", + "home_at_risk_subtext": "Según tu historial de ubicaciones, es posible que hayas estado en contacto o cerca de alguien con COVID-19.", + "home_enable_location": "Permitir acceso a tu ubicación", + "home_mayo_link_heading": "Más información del COVID-19", + "home_mayo_link_label": "de expertos de la Mayo Clinic", + "home_no_contact_header": "No ha habido contacto", + "home_no_contact_subtext": "Según los datos disponibles, no has estado cerca de nadie que haya dado positivo en COVID-19.", + "home_setting_off_header": "Desconocido", + "home_setting_off_subtext": "Si no permites a la app el acceso a tu ubicación no puedes saber si has estado en riesgo", + "home_unknown_header": "Desconocido", + "home_unknown_subtext": "Si no permites a la app el acceso a tu ubicación no puedes saber si has estado en riesgo", + "latest_news": "Últimas noticias", + "launch_done_header": "Ya estás listo", + "launch_done_subheader": "Preparados para comenzar. Recuerda que puedes cambiar tus preferencias más tarde.", + "launch_enable_location": "Permitir acceso", + "launch_enable_notif": "Permitir notificaciones", + "launch_finish_set_up": "Finalizar configuración", + "launch_get_started": "Iniciar", + "launch_location_access": "Acceder a tu ubicación", + "launch_location_header": "Para recordar donde has estado, tu teléfono móvil necesita registrar tu ubicación.", + "launch_location_subheader": "No te preocupes, estos datos nunca saldrán de tu móvil sin tu consentimiento expreso.", + "launch_next": "Siguiente", + "launch_notif_header": "Con las notificaciones, podrás saber si has estado cerca de alguien con COVID.", + "launch_notif_subheader": "No te molestaremos salvo para enviarte información sobre tu riesgo de exposición.", + "launch_notification_access": "No te molestaremos salvo para enviarte información sobre tu riesgo de exposición.", + "launch_screen1_header": "La vuelta a la normalidad comienza aquí.", + "launch_screen2_header": "Recibe notificaciones si has estado con alguien que después dé positivo en COVID-19.", + "launch_screen2_subheader": "La información es poder.", + "launch_screen3_header": "Si das positivo, puedes optar por compartir tus datos de forma anónima", + "launch_screen3_subheader": "Ayuda a proteger a tu entorno más cercano.", + "launch_screen4_header": "Compartir depende de ti. Tus datos solo se guardan en tu móvil.", + "launch_screen4_subheader": "Si has dado positivo, solo tú eliges si quieres compartirlos.", + "launch_set_up_phone": "Configurar mi dispositivo", + "legal_page_title": "Aviso legal", + "loading_public_data": "cargando datos…", + "location_disabled_message": "COVID Safe Paths necesita tu ubicación.", + "location_disabled_title": "Registro de ubicación desactivado", + "location_enabled_message": "COVID Safe Paths está registrando en tu dispositivo tus coordenadas GPS cada 5 minutos de forma segura.", + "location_enabled_title": "COVID Safe Paths habilitado", + "logging_active": "Localización activada", + "logging_inactive": "Localización desactivada", + "news_subtitle": "Novedades sobre el COVID-19 de tus autoridades sanitarias y noticias en general.", + "news_title": "Últimas Noticias", + "no_data": "No hay datos", + "push_at_risk_message": "Has estado cerca de alguien con COVID-19", + "push_at_risk_title": "Puedes estar en riesgo", + "see_exposure_history": "Ver historial de exposición", + "settings_title": "Panel de control", + "team": "Equipo", + "team_para": "El equipo se compone de un consorcio de epidemiólogos, ingenieros, analistas de datos, defensores de la privacidad digital, profesores y investigadores de instituciones reconocidas como MIT, Harvard, la Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young y Link Ventures.", + "terms_of_use": "Condiciones de uso" + }, + "share": { + "button_text": "Comparte tus datos", + "paragraph_first": "Si has dado positivo en COVID-19, ayuda a las autoridades locales compartiendo tu historial de ubicaciones.", + "paragraph_second": "Tu historial se envía como una simple lista de ubicaciones en el tiempo, sin revelar datos personales.", + "subtitle": "Puedes compartir tus datos con las autoridades sanitarias u otros agentes, o guardar una copia de seguridad", + "title": "Compartir ubicaciones" } } diff --git a/app/locales/fil.json b/app/locales/fil.json new file mode 100644 index 0000000000..1dfe61f2b7 --- /dev/null +++ b/app/locales/fil.json @@ -0,0 +1,53 @@ +{ + "import": { + "title": "Mag-import ng lokasyon" + }, + "label": { + "about_title": "Tungkol", + "authorities_add_button_label": "Magdagdag ng Pinagkakatiwalaang Pinagmulan", + "authorities_add_url": "Magdagdag ng awtoridad sa pamamagitan ng URL", + "authorities_input_placeholder": "I-paste ang iyong URL dito", + "authorities_no_sources": "Wala pang mapagkukunan ng data", + "authorities_removal_alert_cancel": "Magkansela", + "authorities_removal_alert_proceed": "Ituloy", + "authorities_removal_alert_title": "Tanggalin ang awtoridad", + "authorities_title": "Pinagkakatiwalaang mga mapagkukunan", + "choose_provider_title": "Pumili ng awtoridad sa kalusugan", + "commitment": "Pangako", + "default_news_site_name": "Safe Paths Balita", + "event_history_title": "Kasaysayan ng paglalantad", + "home_at_risk_header": "Ikaw ay Maaaring Malantad", + "home_at_risk_subsubtext": "Hindi ito nangangahulugang ikaw ay nahawaan.", + "home_enable_location": "Paganahin ang Lokasyon gamit ang Data", + "home_mayo_link_heading": "Karagdagang impormasyon tungkol sa Covid-19", + "home_mayo_link_label": "mula sa Mayo Clinic", + "home_no_contact_header": "Walang nakahalubilo na nagkasakit o may sakit na.", + "home_setting_off_header": "Hindi kilala", + "home_unknown_header": "Hindi kilala", + "latest_news": "Pinakabagong Balita", + "launch_done_header": "Tapos na.", + "launch_enable_location": "Paganahin ang Lokasyon", + "launch_enable_notif": "Paganahin ang notipikasyon", + "launch_finish_set_up": "Tapos na ang Pag-setup", + "launch_get_started": "Magsimula", + "launch_location_access": "Pag-access sa lokasyon", + "launch_location_subheader": "Walang kailangan ipag-alala dahil ang mga impormasyong iyong inilagay ay dito lamang mananatili sa iyong aparato maaliban na lamang kung bibigyan nyo ng pahintulot na ipamahagi ito sa iba.", + "launch_next": "Susunod", + "launch_notif_header": "Kayo ay makakatanggao ng abiso o paalala kung sakali mang kayo ay may nakahalubilo na maysakit", + "launch_notif_subheader": "Ang aming hihilingin lang sa inyo bilang abala ay kung may panganib, maari po lamang na ito ay ibahagi nyo sa amin.", + "launch_notification_access": "Payagan ang mga notipikasyon", + "launch_screen2_header": "Iyong maabisuhan kung ikaw ay cross landas na may isang tao sa ibang pagkakataon nasuri para sa COVID-19." + }, + "onboarding": { + "eula_checkbox": "Tinatanggap ko ang kasunduan nito.", + "eula_continue": "Ituloy" + }, + "version_update": { + "alert_label": "Ang COVID Safe Paths bersyon ay luma na", + "alert_sublabel": "Mag-update sa bagong bersyon?", + "later": "Kalaunan", + "push_notification_message": "Mangyaring i-update ang iyong COVID Safe Paths Software.", + "push_notification_title": "Ang aplikasyon ay luma na", + "update": "I-update" + } +} diff --git a/app/locales/fr.json b/app/locales/fr.json index 3ac03fb376..8a8a9910d4 100644 --- a/app/locales/fr.json +++ b/app/locales/fr.json @@ -1,4 +1,14 @@ { + "import": { + "button_text": "Importer les positions passées", + "google": { + "disclaimer": "L’application Safe Paths n’est pas affiliée à Google et ne partage jamais vos données.", + "title": "Google Maps", + "visit_button_text": "Accédez à Google Takeout" + }, + "subtitle": "Vous pouvez importer votre historique personnel de position pour voir si vous avez croisé une personne infectée avant d’avoir téléchargé cette application.", + "title": "Importer les positions" + }, "label": { "about_title": "À propos", "authorities_add_button_label": "Ajouter une source de confiance", @@ -18,8 +28,6 @@ "default_news_site_name": "Nouvelles SafePaths (en anglais)", "event_history_subtitle": "Comprendre son exposition personnelle à partie des informations partagées par les autorités sanitaires.", "event_history_title": "Historique d’exposition", - "export_para_1": "Si vous êtes diagnostiqué·e positif·ve à la COVID-19, contribuer à l'effort en partageant votre historique de positions avec les autorités locales.", - "export_para_2": "L’historique partagé est une simple liste de lieux et de dates, sans informations supplémentaires.", "home_at_risk_header": "Vous avez pu être exposé·e", "home_at_risk_subsubtext": "Ceci ne signifie pas nécessairement que vous êtes contaminés.", "home_at_risk_subtext": "D’après votre historique de positions GPS, il est possible que vous ayez été en contact ou à proximité d’une personne porteuse du COVID-19.", @@ -28,20 +36,10 @@ "home_mayo_link_label": "de l’ONG Mayo Clinic (en anglais)", "home_no_contact_header": "Pas de contacts connus", "home_no_contact_subtext": "D’après les données disponibles, vous n’avez pas été à proximité de porteur·ses du COVID-19.", - "home_unknown_header": "Pas de données", - "home_unknown_subtext": "Nous ne pouvons déterminer si vous êtes à risque si vous n’autorisez pas l’application à accéder à votre position.", "home_setting_off_header": "Pas de données", "home_setting_off_subtext": "Nous ne pouvons déterminer si vous êtes à risque si vous n’autorisez pas l’application à accéder à votre position.", - "import_title": "Importer les positions", - "import_step_1": "1. Rajouter des données de positions de Google vous donnera une avance pour connaître vos positions récentes", - "import_step_2": "2. Avant d'importer, vous devez prendre vos données de Google Takeout.", - "import_step_3":"Visitez Google Takeout et exportez votre historique des positions avec ces paramètres: \n1. Méthode de livraison: \"Ajouter à Google Drive\" \n2. Fréquence: \"Exporter une seule fois\" \n3. Type & taille de fichier: \".zip\" et \"1GB\" \n4. Google envoie un courriel quand l'exportation est finie \n5. Revenir ici pour importer les positions. Options d'importation: \n- Importer de Google Drive \n- Télécharger du navigateur, ensuite importer des fichiers locaux. Soyez sur d'être sur le réseau WiFi puisque les fichiers peuvent être gros.", - "import_takeout":"Accédez à Google Takeout", - "import_success":"Les positions récentes ont été importées!", - "import_already_imported":"Le fichier Takeout fourni a déjà été importé.", - "import_error":"Un erreur s'est produite lors de l'importation de vos données.", - "import_no_recent_locations": "Takeout n'a pas de positions récentes.", - "import_invalid_file_format": "Le format de fichier fourni n'est pas supporté. \n Formats supportés: \".zip\".", + "home_unknown_header": "Pas de données", + "home_unknown_subtext": "Il est nécessaire d’autoriser l’application à accéder à votre position pour déterminer les risques d’exposition.", "latest_news": "Dernières nouvelles", "launch_done_header": "Terminé", "launch_done_subheader": "Tout est prêt. Rappelez vous que vous pouvez toujours revenir ajuster vos préférences.", @@ -65,43 +63,26 @@ "launch_screen4_subheader": "Si vous êtes diagnostiqué·e positif·ve, vous seul·e pourrez choisir de partager vos données.", "launch_set_up_phone": "Configurer mon appareil", "legal_page_title": "Juridique", - "less_than_one_minute": "moins d’une minute", "loading_public_data": "chargement des données...", "location_disabled_message": "COVID Safe Paths a besoin des services de position.", "location_disabled_title": "Le suivi de la position a été désactivé", - "location_enabled_message": "COVID Safe Paths enregistre de façon sécurisée vos coordonnées GPS toutes les cinq minutes sur cet appareil..", + "location_enabled_message": "COVID Safe Paths enregistre de façon sécurisée vos coordonnées GPS toutes les cinq minutes sur cet appareil.", "location_enabled_title": "COVID Safe Paths activé", - "maps_import_button_text": "Importer les positions passées", - "maps_import_disclaimer": "L’application Safe Paths n’est pas affiliée à Google et ne partage jamais vos données.", - "maps_import_text": "Vous pouvez importer votre historique personnel de position pour voir si vous avez croisé une personne infectée avant d’avoir téléchargé cette application.", - "maps_import_title": "Google Maps", - "nCoV2019_url_info": "Plus d’informations sur les données représentées sur cette carte", "news_subtitle": "Obtenez les dernières nouvelles sur le COVID-19 par vos autorités sanitaires et en général.", "news_title": "Dernières nouvelles", "no_data": "Pas de données", - "notification_2_weeks_ago": "Il y a 2 semaines", - "notification_data_not_available": "Pas de données d’exposition disponibles.", - "notification_random_data_button": "Choisir une autorité sanitaire", - "notification_title": "Profil des expositions sur 2 semaines", - "notification_today": "aujourd’hui", - "notification_warning_text": "Si une autorité sanitaire dans votre région a mis en place Safe Paths, vous pouvez abonner pour obtenir des mises à jours régulières sur les risques d’exposition.", - "notifications_exposure_format": "Vous avez croisé une personne infectée il y a {{daysAgo}} jours pendant {{exposureTime}} minutes.", - "notifications_exposure_format_today": "Vous avez croisé une personne infectée aujourd’hui pendant {{exposureTime}} minutes.", - "notifications_exposure_format_yesterday": "Vous avez croisé une personne infectée hier pendant {{exposureTime}} minutes.", - "notifications_no_exposure": "Pas d’expositions connues à la COVID-19 durant les deux dernières semaines.", - "overlap_found_button_label": "Données publiques chargées", - "overlap_no_results_button_label": "Données publiques chargées", - "overlap_para_1": "Le chemin vert représente votre historique de positions\n\nLes cercles violets clairs représentes les données publiques", - "overlap_title": "Chercher les recoupements", "push_at_risk_message": "Vous avez croisé une personne infectée à la COVID-19", "push_at_risk_title": "Vous avez pu être exposé", "settings_title": "Réglages", - "share_location_data": "Partager les données de position", - "show_overlap": "Cliquez pour voir les données publiques", "team": "L’équipe", "team_para": "Notre équipe est un consortium composé d’épidémiologistes, d’ingénieur·es, de scientifiques des données, de défenseurs de la vie privée numérique, de professeur·es et de chercheur·ses d’institutions réputées, dont le MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young, et Link Ventures.", - "terms_of_use": "Conditions d’utilisation (en anglais)", - "tested_positive_subtitle": "Vos données de position peuvent être envoyées aux autorités sanitaires, sauvegardées, ou partagées autrement.", - "tested_positive_title": "Partager votre historique de position" + "terms_of_use": "Conditions d’utilisation (en anglais)" + }, + "share": { + "button_text": "Partager les données de position", + "paragraph_first": "Si vous êtes diagnostiqué·e positif·ve à la COVID-19, partager votre historique de positions avec les autorités locales contribuera à endiguer la maladie.", + "paragraph_second": "L’historique partagé est une simple liste de lieux et de dates, sans informations supplémentaires.", + "subtitle": "Vos données de position peuvent être envoyées aux autorités sanitaires, sauvegardées, ou partagées autrement.", + "title": "Partager votre historique de position" } } diff --git a/app/locales/ht.json b/app/locales/ht.json index 2db9b5f06e..9a769a7353 100644 --- a/app/locales/ht.json +++ b/app/locales/ht.json @@ -3,12 +3,21 @@ "no_exposure": "Pa gen kontak", "possible_exposure": "Posib", "possible_exposure_para": "Li posib ke ou te an kontak oswa pre yon moun ki teste pozitif pou Kowonaviris", - "timeline": "Kalandriye", "what_does_this_mean": "Ki sa sa a vle di?", "what_does_this_mean_para": "Daprè lis done GPS ou, li posib ke ou te an kontak oswa tou pre yon moun ki te dyagnostike ak Kowonaviris. Sa pa vle di ou enfekte men se yon posiblite. \n\n Pou plis enfòmasyon sou sa ou ta dwe fè ou ka al gade sou sit entènèt Mayo Klinik la.", "what_if_no_symptoms": "E si mwen pa genyen sentòm yo?", "what_if_no_symptoms_para": "Si ou pa gen okenn sentòm men ou ta renmen fè tès la, ou ka ale nan lokal tès ki pi pre w la. \n\n Moun ki pa montre sentòm yo ka pafwa toujou gen enfeksyon an e enfekte lòt moun. Lèw pran prekosyon nan sa ki gen pou wè ak distans sosyal ak antre an kontak ak gwoup anoil moun oswa moun ki gen risk (granmoun aje a, moun ki gen lòt pwoblèm sante) enpòtan nan jere risk pou tèt pa w ak risk pou lòt moun." }, + "import": { + "button_text": "Voye lokalizasyon ki pase yo", + "google": { + "disclaimer": "Chase Kowona (\"Safe Paths\") pa gen okenn afilyasyon ak Google epi li pa janm pataje done ou yo.", + "title": "Kat Google", + "visit_button_text": "Vizite Google Takeout" + }, + "subtitle": "Pou wè si ou rankontre yon moun ki gen Kowonaviris, telechaje aplikasyon sa a avan, ou kapab voye lis localizasyon pèsonèl ou.", + "title": "Pran done lokalizasyon" + }, "label": { "about_title": "Apropo", "authorities_add_button_label": "Ajoute yon sous ou fè konfyans", @@ -28,8 +37,6 @@ "default_news_site_name": "Nouvèl sou Chase Kowona (\"Safe Paths\")", "event_history_subtitle": "Konprann ekspozisyon pèsonèl ou daprè enfòmasyon Otorite a pataje.", "event_history_title": "Lis ekspozisyon", - "export_para_1": "Si ou teste pozitif pou Kowonaviris, tanpri fè pati pa ou a nan pataje lis lokalizasyon ou ak otorite lokal yo.", - "export_para_2": "Lokalizasyon ou yo ap pataje sou fòm yon senp lis lè ak zòn, pap gen okenn enfòmasyon anplis ki prale.", "home_at_risk_header": "Ou ka gen risk", "home_at_risk_subsubtext": "Sa pa vle di ou enfekte.", "home_at_risk_subtext": "Daprè done disponib yo, ou te kwaze chemen ak yon moun ki te teste pozitif pou Kowonaviris.", @@ -42,13 +49,7 @@ "home_setting_off_subtext": "Nou pa ka di si w gen risk sof si ou aktive lis lokalizasyon nan tablodbò a", "home_unknown_header": "Enkoni", "home_unknown_subtext": "Nou pa ka di si ou nan risk sof si ou pèmèt app la gen aksè a lokalizasyon ou.", - "import_step": "{\"one\": \"Ajoute done lokalizasyon ki sòti sou Google ap ba ou komansman yon ide sou dènye lokalizasyon ou yo.\", \"other\": \"Anvan ou ka enpòte, ou dwe 'Pran' done lokalizasyon ou yo sou Google avan.\"}", - "import_takeout": "Vizite Google Takeout", - "import_title": "Pran done lokalizasyon", - "language_change_alert_cancel": "Anile", - "language_change_alert_proced": "Rekòmanse", - "language_change_alert_title": "Aplikasyon an bezwen rekòmanse dèyè nèt pou itiize lang sa ", - "latest_news": "Dènye nouvèl ", + "latest_news": "Dènye nouvèl", "launch_done_header": "Fini nèt", "launch_done_subheader": "Ou pare pouw woule. Sonje byen, ou ka toujou fè mizajou sou preferans ou yo pita.", "launch_enable_location": "Aktive lokalizasyon", @@ -67,11 +68,10 @@ "launch_screen2_subheader": "Konesans se pouvwa", "launch_screen3_header": "Si ou teste pozitif, ou ka pataje done pouw ede lòt moun pandan wap kenbe idantite ou sekrè.", "launch_screen3_subheader": "Èd sa yo ede kenbe tout kominote a an sekirite.", - "launch_screen4_header": "Ou gen kontròl tout bagay. Done ou yo anrejistre selman sou telefòn ou", + "launch_screen4_header": "Ou gen kontwòl tout bagay. Done ou yo anrejistre selman sou telefòn ou", "launch_screen4_subheader": "Si ou teste pozitif, sèlman ou menm kapab chwazi si wi ou non wap pataje sa.", "launch_set_up_phone": "Fikse telefòn mwen", "legal_page_title": "Legal", - "less_than_one_minute": "Mwens pase 1 minit", "loading_public_data": "Chajman done...", "location_disabled_message": "Chase Kowona (\"Safe Paths\") mande sèvis lokalizasyon", "location_disabled_title": "GPS ou te dezaktive", @@ -79,43 +79,22 @@ "location_enabled_title": "Chase Kowona (\"Safe Paths\") active", "logging_active": "Lokalizasyon aktif", "logging_inactive": "Lokalizasyon inaktif", - "maps_import_button_text": "Voye lokalizasyon ki pase yo", - "maps_import_disclaimer": "Chase Kowona (\"Safe Paths\") pa gen okenn afilyasyon ak Google epi li pa janm pataje done ou yo.", - "maps_import_text": "Pou wè si ou rankontre yon moun ki gen Kowonaviris, telechaje aplikasyon sa a avan, ou kapab voye lis localizasyon pèsonèl ou.", - "maps_import_title": "Kat Google", - "nCoV2019_url_info": "Pou plis enfòmasyon sou done pou kat sa a", - "news_subtitle": "Li dènye mizajou sou Kowonaviris ak videyo, atik, ilistrasyon ak lòt resous ke ou ka itilize pou jere sitiyasyon an pi byen. ", + "news_subtitle": "Li dènye mizajou sou Kowonaviris ak videyo, atik, ilistrasyon ak lòt resous ke ou ka itilize pou jere sitiyasyon an pi byen.", "news_title": "Dènye nouvèl", "no_data": "Pa gen okenn done", - "notification_2_weeks_ago": "2 semenn pase", - "notification_data_not_available": "Pa gen done ekspozisyon ki disponib.", - "notification_select_authority": "Chwazi Otorite a", - "notification_title": "Ekspozisyon Pwofil pou 2 semèn", - "notification_today": "Jodi a", - "notification_warning_text": "Si yon Otorite egziste nan zòn ou an ou kapab pran abònman pou jwenn dènye enfòmasyon si gen risk ekspozisyon", - "notifications_exposure_format": "{{daysAgo}} jou pase, ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", - "notifications_exposure_format_today": "Jodi a ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", - "notifications_exposure_format_yesterday": "Yè ou kwaze chemen avèk yon moun enfekte pandan {{exposureTime}} minit.", - "notifications_no_exposure": "Pa gen ekspozisyon a Kowonaviris ki enrejistre pandan de semèn ki sot pase yo.", - "overlap_found_button_label": "Done Piblik Chaje", - "overlap_no_results_button_label": "Done Piblik Chaje", - "overlap_para_1": "Pati vèt yo reprezante lis lokalizasyon ou\nWonn mov pal yo reprezante done piblik yo", - "overlap_title": "Tcheke sipèpozisyton yo", "push_at_risk_message": "Ou te kwaze chemen avèk yon moun ki enfekte Kowonaviris", "push_at_risk_title": "Ou ka gen risk", "see_exposure_history": "Wè lis ekspozisyon", "settings_title": "Tablodbò", - "share_location_data": "Pataje done lokalizasyon", - "show_overlap": "Klike sou yo pou wè done piblik yo", "team": "Ekip", "team_para": "Ekip nou an konpoze de yon group ki genyen ladanl epidemyolojis, enjenyè, syantis done, evanjeliS vi prive dijital, pwofesè ak chèchè nan anpil gran enstitisyon nan mond lan, tankou: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young ak Link Ventures.", - "terms_of_use": "Règleman itilizasyon", - "tested_positive_subtitle": "Done prive ou kapab transfere a Otorite a, konsève, oubyen pataje.", - "tested_positive_title": "Pataje lis lokalizasyon" + "terms_of_use": "Règleman itilizasyon" }, - "onboarding": { - "eula_checkbox": "Mwen aksepte akò lisans la", - "eula_message": "*Ou dwe aksepte pou itilize Safe Paths 'Chase Kowona", - "eula_continue": "Kontinye" + "share": { + "button_text": "Pataje done lokalizasyon", + "paragraph_first": "Si ou teste pozitif pou COVID-19, tanpri fè pati pa ou nan pataje list pozisyon ou ak otorite lokal yo.", + "paragraph_second": "Lokalizasyon yo prale nan fòm yon senp lis lè ak kowòdone zòn yo, okenn enfòmasyon anplis pap transfere", + "subtitle": "Done prive ou kapab transfere a Otorite a, konsève, oubyen pataje.", + "title": "Pataje lis lokalizasyon" } } diff --git a/app/locales/id.json b/app/locales/id.json index 1dcb7825c7..02eb130b3a 100644 --- a/app/locales/id.json +++ b/app/locales/id.json @@ -1,4 +1,22 @@ { + "history": { + "no_exposure": "Tidak ada paparan", + "possible_exposure": "Kemungkinan Terpapar", + "possible_exposure_para": "Mungkin saja Anda berhubungan atau dekat dengan seseorang yang positif COVID-19", + "what_does_this_mean": "Apa artinya ini?", + "what_does_this_mean_para": "Berdasarkan riwayat GPS Anda, ada kemungkinan bahwa Anda telah melakukan kontak dengan atau dekat dengan seseorang yang didiagnosis COVID-19. Ini tidak berarti Anda terinfeksi tetapi Anda mungkin terinfeksi.\n\nUntuk informasi lebih lanjut tentang apa yang harus Anda lakukan, Anda dapat merujuk ke situs web Mayo Clinic.", + "what_if_no_symptoms": "Bagaimana jika saya tidak menunjukkan gejala?", + "what_if_no_symptoms_para": "Jika Anda tidak memiliki gejala tetapi masih ingin diuji, Anda dapat pergi ke fasilitas pengujian terdekat.\n\n Individu yang tidak menunjukkan gejala kadang-kadang masih dapat mengalami infeksi dan menginfeksi orang lain. Berhati-hati dengan jarak sosial dan melakukan kontak dengan kelompok besar atau individu yang berisiko (orang tua, mereka yang memiliki masalah medis lainnya) adalah penting untuk mengelola risiko Anda dan risiko terhadap orang lain." + }, + "import": { + "button_text": "Impor lokasi sebelumnya", + "google": { + "disclaimer": "Safe Paths tidak memiliki afiliasi dengan Google dan tidak pernah membagikan data Anda.", + "title": "Google Maps" + }, + "subtitle": "Untuk melihat apakah Anda pernah bertemu seseorang dengan COVID-19 sebelum mengunduh aplikasi ini, Anda dapat mengimpor riwayat lokasi pribadi Anda.", + "title": "Impor Data Lokasi" + }, "label": { "about_title": "Tentang", "authorities_add_button_label": "Tambah Sumber Terpercaya", @@ -18,8 +36,6 @@ "default_news_site_name": "Berita Safe Paths", "event_history_subtitle": "Pahami paparan pribadi Anda berdasarkan informasi yang dibagikan oleh otoritas kesehatan.", "event_history_title": "Riwayat paparan", - "export_para_1": "Jika Anda dinyatakan positif COVID-19, Bertanggung Jawablah dengan membagikan riwayat lokasi Anda dengan otoritas kesehatan.", - "export_para_2": "Lokasi yang dibagikan berupa tempat dan waktu, tidak ada informasi tambahan yang lain", "home_at_risk_header": "Anda Mungkin Terpapar", "home_at_risk_subsubtext": "Ini tidak berarti Anda terinfeksi.", "home_at_risk_subtext": "Berdasarkan riwayat GPS Anda, ada kemungkinan Anda melakukan kontak atau dekat dengan seseorang yang didiagnosis dengan COVID-19.", @@ -30,9 +46,6 @@ "home_no_contact_subtext": "Berdasarkan data yang tersedia Anda belum berada di dekat orang yang dilaporkan positif untuk COVID-19.", "home_unknown_header": "Tidak dikenal", "home_unknown_subtext": "Kami tidak dapat memberi tahu apakah Anda berisiko atau tidak, kecuali Anda mengizinkan aplikasi untuk mengakses lokasi Anda.", - "import_step_1": "1. Masuk ke Akun Google Anda dan Unduh Riwayat Lokasi Anda", - "import_step_2": "2. Setelah diunduh, buka layar ini lagi. Data akan diimpor secara otomatis.", - "import_title": "Impor Data Lokasi", "latest_news": "Berita Terbaru", "launch_done_header": "Selesai", "launch_done_subheader": "Anda siap. Ingat, Anda selalu dapat memperbarui preferensi Anda nanti.", @@ -56,54 +69,27 @@ "launch_screen4_subheader": "Jika Anda dinyatakan positif, Anda dapat memilih untuk berbagi atau tidak.", "launch_set_up_phone": "Siapkan Perangkat Saya", "legal_page_title": "Hukum", - "less_than_one_minute": "kurang dari 1 menit", "loading_public_data": "memuat data...", "location_disabled_message": "COVID Safe Paths membutuhkan layanan lokasi.", "location_disabled_title": "Pelacakan Lokasi Dinonaktifkan", "location_enabled_message": "COVID Safe Paths akan menyimpan lokasi anda setiap 5 menit sekali", "location_enabled_title": "COVID Safe Paths Diaktifkan", - "maps_import_button_text": "Impor lokasi sebelumnya", - "maps_import_disclaimer": "Safe Paths tidak memiliki afiliasi dengan Google dan tidak pernah membagikan data Anda.", - "maps_import_text": "Untuk melihat apakah Anda pernah bertemu seseorang dengan COVID-19 sebelum mengunduh aplikasi ini, Anda dapat mengimpor riwayat lokasi pribadi Anda.", - "maps_import_title": "Google Maps", - "nCoV2019_url_info": "Informasi lanjutan tentang dataset ini", "news_subtitle": "Baca Informasi COVID terbaru dari otoritas kesehatan Anda", "news_title": "Berita Terbaru", "no_data": "Tidak ada data", - "notification_2_weeks_ago": "2 minggu lalu", - "notification_data_not_available": "Tidak ada data paparan yang tersedia.", - "notification_select_authority": "Pilih Otoritas Kesehatan", - "notification_title": "Profil Paparan 2 Minggu", - "notification_today": "hari ini", - "notification_warning_text": "Jika Otoritas Kesehatan ada di daerah Anda, Anda dapat berlangganan untuk mendapatkan pembaruan rutin risiko paparan.", - "notifications_exposure_format": "{{daysAgo}} hari lalu anda berada dalam radius orang yang terpapar selama {{exposureTime}} Menit.", - "notifications_exposure_format_today": "Hari ini anda berpapasan dengan orang yang terinfeksi selama {{exposureTime}} menit.", - "notifications_exposure_format_yesterday": "Kemarin anda berpapasan dengan orang yang terinfeksi selama {{exposureTime}} menit.", - "notifications_no_exposure": "Tidak ada data paparan COVID-19 untuk dua minggu terakhir.", - "overlap_found_button_label": "Data Publik Dimuat", - "overlap_no_results_button_label": "Data Publik Dimuat", - "overlap_para_1": "Jejak hijau menunjukkan riwayat lokasi Anda\n\nLingkaran ungu muda mewakili dataset publik", - "overlap_title": "Periksa Overlap", "push_at_risk_message": "Anda telah berpapasan dengan pasien COVID-19", "push_at_risk_title": "Anda mungkin berisiko", "see_exposure_history": "Lihat riwayat paparan", "settings_title": "Dashboard", - "share_location_data": "Bagikan riwayat lokasi", - "show_overlap": "Click untuk melihat data publik", "team": "Tim", "team_para": "Tim kami terdiri dari konsorsium ahli epidemiologi, insinyur, ilmuwan, ahli privasi digital, profesor dan peneliti dari lembaga terkemuka, termasuk: MIT, Harvard, Klinik Mayo, TripleBlind, EyeNetra, Ernst & Young dan Link Ventures.", - "terms_of_use": "Syarat Penggunaan", - "tested_positive_subtitle": "Data pribadi Anda dapat ditransfer ke otoritas kesehatan, dicadangkan, atau dibagikan lain.", - "tested_positive_title": "Bagikan riwayat lokasi" + "terms_of_use": "Syarat Penggunaan" }, - "history": { - "no_exposure": "Tidak ada paparan", - "possible_exposure_para": "Mungkin saja Anda berhubungan atau dekat dengan seseorang yang positif COVID-19", - "possible_exposure": "Kemungkinan Terpapar", - "timeline": "Linimasa", - "what_does_this_mean_para": "Berdasarkan riwayat GPS Anda, ada kemungkinan bahwa Anda telah melakukan kontak dengan atau dekat dengan seseorang yang didiagnosis COVID-19. Ini tidak berarti Anda terinfeksi tetapi Anda mungkin terinfeksi.\n\nUntuk informasi lebih lanjut tentang apa yang harus Anda lakukan, Anda dapat merujuk ke situs web Mayo Clinic.", - "what_does_this_mean": "Apa artinya ini?", - "what_if_no_symptoms_para": "Jika Anda tidak memiliki gejala tetapi masih ingin diuji, Anda dapat pergi ke fasilitas pengujian terdekat.\n\n Individu yang tidak menunjukkan gejala kadang-kadang masih dapat mengalami infeksi dan menginfeksi orang lain. Berhati-hati dengan jarak sosial dan melakukan kontak dengan kelompok besar atau individu yang berisiko (orang tua, mereka yang memiliki masalah medis lainnya) adalah penting untuk mengelola risiko Anda dan risiko terhadap orang lain.", - "what_if_no_symptoms": "Bagaimana jika saya tidak menunjukkan gejala?" + "share": { + "button_text": "Bagikan riwayat lokasi", + "paragraph_first": "Jika Anda dinyatakan positif COVID-19, Bertanggung Jawablah dengan membagikan riwayat lokasi Anda dengan otoritas kesehatan.", + "paragraph_second": "Lokasi yang dibagikan berupa tempat dan waktu, tidak ada informasi tambahan yang lain", + "subtitle": "Data pribadi Anda dapat ditransfer ke otoritas kesehatan, dicadangkan, atau dibagikan lain.", + "title": "Bagikan riwayat lokasi" } } diff --git a/app/locales/it.json b/app/locales/it.json index 1d80c723de..6b40a894d0 100644 --- a/app/locales/it.json +++ b/app/locales/it.json @@ -1,14 +1,23 @@ { - "Done": "Fatto", "history": { - "no_exposure": "Nessuna esposizione", - "possible_exposure": "Possibile esposizione", + "no_exposure": "Non noto", + "possible_exposure": "Possibile", "possible_exposure_para": "E' possibile che tu sia stato in contatto o vicino ad una persona risultata positiva al COVID-19.", "what_does_this_mean": "Cosa significa?", "what_does_this_mean_para": "In base alla tua cronologia GPS, è possibile che tu sia stato in contatto o nelle vicinanze di una persona risultata positiva al COVID-19. Questo però non significa che tu sia infetto.\n\nPer maggiori informazioni o per sapere cosa fare, fai riferimento al sito della Mayo Clinic.", "what_if_no_symptoms": "E se non mostro nessun sintomo?", "what_if_no_symptoms_para": "Se non hai alcun sintomo, ma vuoi comunque effettuare un test, puoi presentarti al centro più vicino.\n\nLe persone che non mostrano alcun sintomo possono, in alcune circostanze, essere ancora contagiosi. Mantieni alta l'attenzione e pratica il distanziamento sociale, evita di venire in contatto con gruppi di persone o con persone ad alto rischio (anziani o persone affette da altre patologie), è importante non solo per te, ma anche per gli altri." }, + "import": { + "button_text": "Importa lo storico delle posizioni", + "google": { + "disclaimer": "Safe Paths non ha alcuna affiliazione a Google e non condivide mai i tuoi dati", + "title": "Google Maps", + "visit_button_text": "Visita Google Takeout" + }, + "subtitle": "Per capire se hai incontrato qualcuno con COVID-19 prima di scaricare questa app, puoi importare lo storico delle tue posizioni", + "title": "Importa posizioni" + }, "label": { "about_title": "Informazioni", "authorities_add_button_label": "Aggiungi istituzione sanitaria", @@ -28,8 +37,6 @@ "default_news_site_name": "Novità su Safe Paths", "event_history_subtitle": "Controlla la tua possibile esposizione sulla base delle informazioni condivise dalle istituzioni sanitarie", "event_history_title": "Storico delle esposizioni", - "export_para_1": "Se risulti COVID-19 positivo, per favore aiutaci condividendo lo storico delle tue posizioni con le istituzioni sanitarie.", - "export_para_2": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva.", "home_at_risk_header": "Potresti esser stato esposto", "home_at_risk_subsubtext": "Questo non significa che tu sia infetto", "home_at_risk_subtext": "Sulla base del tuo storico GPS, è possibile che tu sia venuto a contatto o sia nelle vicinanze di qualcuno con diagnosi di COVID-19", @@ -42,11 +49,6 @@ "home_setting_off_subtext": "Non possiamo darti informazioni sull'esposizione al virus se non attivi la localizzazione.", "home_unknown_header": "Sconosciuto", "home_unknown_subtext": "Non è possibile sapere se sei stato esposto se non permetti alla app di accedere alla tua posizione", - "import_step_1": "Aggiungere lo storico delle localizzazioni da Google ti aiuta ad avere piu informazioni sulle tue posizioni passate.", - "import_step_2": "Prima di importare i dati, devi effettuare il 'Take out' dei dati dal sito di Google.", - "import_step_3": "Apri il sito di Google Takeout ed effettua l'export dei tuoi dati di posizione seguendo i seguenti step:\n1. Metodo di recapito: \"Aggiungi a Drive\"\n Frequenza: \"Esporta un archivio\"\n3. Tipo di file e dimensioni: \".zip\" e \"1GB\"\n4. Google ti invierà un messaggio via email al termine dell'esportazione.\n5. Ritorna in questa schermata per effettuare l'importazione dei dati.\n- Importa da Google Drive\n- Scarica con il browser e poi importa manualmente.\n Ti consigliamo l'uso del WiFi in quanto il file potrebbe essere di grandi dimensioni.", - "import_takeout": "Visita Google Takeout", - "import_title": "Importa posizioni", "latest_news": "Ultime novità", "launch_done_header": "Fatto!", "launch_done_subheader": "Sei pronto. Ricorda, puoi sempre modificare le impostazioni più tardi.", @@ -77,10 +79,6 @@ "location_enabled_title": "COVID Safe Paths è abilitato", "logging_active": "Localizzazione attiva", "logging_inactive": "Localizzazione disattiva", - "maps_import_button_text": "Importa lo storico delle posizioni", - "maps_import_disclaimer": "Safe Paths non ha alcuna affiliazione a Google e non condivide mai i tuoi dati", - "maps_import_text": "Per capire se hai incontrato qualcuno con COVID-19 prima di scaricare questa app, puoi importare lo storico delle tue posizioni", - "maps_import_title": "Google Maps", "news_subtitle": "Leggi gli aggiornamenti su COVID-19 da parte delle tue istituzioni sanitarie e nel mondo", "news_title": "Ultime novità", "no_data": "Nessun dato", @@ -88,11 +86,28 @@ "push_at_risk_title": "Potresti essere a rischio di esposizione", "see_exposure_history": "Visualizza lo storico delle esposizioni", "settings_title": "Settaggi", - "share_location_data": "Condividi i tuoi dati GPS", "team": "Team", "team_para": "Il nostro team è composto da un consorzio di epidemiologi, ingegneri, data scientist, esperti di privacy digitale, professori e ricercatori dalle più importanti istituzioni, incluse: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young e Link Ventures", - "terms_of_use": "Termini per l'utilizzo", - "tested_positive_subtitle": "I tuoi dati personali possono essere trasferiti alle istituzioni sanitarie, salvati o condivisi in altra maniera", - "tested_positive_title": "Condividi lo storico delle posizioni" + "terms_of_use": "Termini per l'utilizzo" + }, + "onboarding": { + "eula_checkbox": "Accetto il contratto di licenza", + "eula_continue": "Continua", + "eula_message": "* È necessario accettare per utilizzare Safe Paths" + }, + "share": { + "button_text": "Condividi i tuoi dati GPS", + "paragraph_first": "Se risulti COVID-19 positivo, per favore aiutaci condividendo lo storico delle tue posizioni con le autorità sanitarie", + "paragraph_second": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva", + "subtitle": "I tuoi dati personali possono essere trasferiti alle istituzioni sanitarie, salvati o condivisi in altra maniera", + "title": "Condividi lo storico delle posizioni" + }, + "version_update": { + "alert_label": "COVID Safe Paths non è aggiornata", + "alert_sublabel": "Vuoi aggiornare ad una nuova versione?", + "later": "Più tardi", + "push_notification_message": "Per favore aggiorna l'applicazione", + "push_notification_title": "L'applicazione non è aggiornata", + "update": "Aggiorna" } } diff --git a/app/locales/ja.json b/app/locales/ja.json new file mode 100644 index 0000000000..0d70d7f294 --- /dev/null +++ b/app/locales/ja.json @@ -0,0 +1,85 @@ +{ + "import": { + "button_text": "過去の位置情報をインポートする", + "google": { + "disclaimer": "セーフパス は Google とは一切提携しておらず、\nあなたのデータをGoogleと共有することはありません。", + "title": "Google マップ" + }, + "subtitle": "このアプリをダウンロードする前に新型コロナウイルス感染者と遭遇したかを確認するために、位置情報履歴をインポートできます。", + "title": "位置情報のインポート" + }, + "label": { + "about_title": "セーフパスについて", + "authorities_add_button_label": "信頼できるソースを追加", + "authorities_add_url": "URLから情報機関を追加", + "authorities_desc": "お住まいの地域の信頼できる医療機関を選択して、感染接触のデータを入手してください。グローバルレジストリから名前を選択、もしくはセーフパスに対応している当局から提供されたウェブアドレスを入力します。", + "authorities_input_placeholder": "ここにURLをペーストしてください", + "authorities_no_sources": "データソースがありません", + "authorities_removal_alert_cancel": "キャンセル", + "authorities_removal_alert_desc": "この機関のデータソースを削除しても良いですか?", + "authorities_removal_alert_proceed": "次へ進む", + "authorities_removal_alert_title": "情報機関の削除", + "authorities_title": "信頼できる情報源", + "choose_provider_subtitle": "感染リスクの情報を得るためには保健当局の情報サービスに加入する必要があります", + "choose_provider_title": "保健当局を選択してください", + "commitment": "取り組み", + "commitment_para": "セーフパスは、あなたの位置情報を使用している人とのやりとりを安全に記録・確認します。あなたのデータは同意なしにあなたの携帯電話から引き出されません", + "default_news_site_name": "セーフパス ニュース", + "event_history_subtitle": "保健当局が共有している情報をもとに、感染リスクの状況を把握しましょう。", + "event_history_title": "感染リスクの履歴", + "home_at_risk_header": "感染リスクに晒された危険性があります", + "home_at_risk_subsubtext": "これは必ずしも感染しているという意味ではありません。", + "home_at_risk_subtext": "あなたのGPS履歴から、新型コロナウイルス感染者と接触していたか、感染者の近くにいた可能性があります。", + "home_enable_location": "位置情報を有効にする", + "home_mayo_link_heading": "新型コロナウイルス感染症について", + "home_mayo_link_label": "メイヨー・クリニックより", + "home_no_contact_header": "感染者との接触は確認されません", + "home_no_contact_subtext": "現在までのデータによると、あなたは新型コロナウイルス陽性者の近くにはいませんでした。", + "home_setting_off_header": "不明", + "home_setting_off_subtext": "アプリに位置情報へのアクセスを有効にしないと感染リスクは判断できません。", + "home_unknown_header": "不明", + "home_unknown_subtext": "アプリに位置情報へのアクセスを有効にしないと感染リスクは判断できません。", + "latest_news": "最新のニュース", + "launch_done_header": "全て完了しました", + "launch_done_subheader": "これで準備完了です。いつでも後で設定は更新する事ができます。", + "launch_enable_location": "位置情報を有効にする", + "launch_enable_notif": "お知らせを有効にする", + "launch_finish_set_up": "セットアップを完了する", + "launch_get_started": "開始する", + "launch_location_access": "ロケーションのアクセス", + "launch_location_header": "あなたが行く場所を覚えておくために、携帯電話はあなたの場所を保存する必要があります。", + "launch_location_subheader": "ご安心ください。あなたが共有することを決めない限り、情報はあなたのデバイスから離れることはありません。", + "launch_next": "次へ進む", + "launch_notif_header": "通知は、あなたが感染者と遭遇した場合に通知されます。", + "launch_notif_subheader": "私たちは、潜在的な感染リスクに関する最新情報を共有する以外は、あなたにご迷惑をかけることはありません。", + "launch_notification_access": "お知らせを有効にする", + "launch_screen1_header": "普通の生活に戻る道はここから始まります。", + "launch_screen2_header": "後に新型コロナウイルス感染者と遭遇していた場合は通知を受けましょう。", + "launch_screen2_subheader": "知識は力です。", + "launch_screen3_header": "もし陽性と診断された場合、あなたのデータを匿名で提供する事ができます。", + "launch_screen3_subheader": "それはあなたのコミュニティ全体の安全を守るのに役立ちます。", + "launch_screen4_header": "あなたは自分のデータを完全にコントールをできます。データはあなたの携帯にしか保存されません。", + "launch_screen4_subheader": "検査で陽性になった場合、共有するかどうかは自分で決めることができます。", + "launch_set_up_phone": "携帯の設定をする", + "legal_page_title": "リーガル", + "loading_public_data": "データをロードしています", + "location_disabled_message": "COVIDセーフパスは位置情報サービスが必要です", + "location_disabled_title": "位置情報のトラッキングが有効化されていません", + "location_enabled_message": "COVID セーフパスは、このデバイスに5分に1回、あなたのGPS情報を安全に保存します。", + "location_enabled_title": "COVID セーフパスが有効化されています", + "news_subtitle": "健康当局からの新型感染ウイルス、その他の最新のアップデートを読む", + "news_title": "最新のニュース", + "no_data": "データがありません", + "push_at_risk_message": "新型コロナウイルス感染症感染者と接触しました。", + "push_at_risk_title": "感染リスクに晒された危険性があります", + "settings_title": "ダッシュボード", + "team": "チーム", + "team_para": "私たちのチームは、MIT、ハーバード大学、メイヨークリニック、トリプルブラインド、アイネトラ、アーンスト&ヤング、リンクベンチャーズなどに属す疫学者、エンジニア、データサイエンティスト、デジタル・プライバシー・エバンジェリスト、大学教員、研究者からなるコンソーシアムで構成されています。", + "terms_of_use": "利用規約" + }, + "share": { + "button_text": "位置情報のシェアをする", + "subtitle": "あなたのデータは保険医療機関に転送、バックアップ、または共有する事ができます。", + "title": "位置情報履歴を共有する" + } +} diff --git a/app/locales/languages.js b/app/locales/languages.js index d5efb123f9..48706e4fd7 100644 --- a/app/locales/languages.js +++ b/app/locales/languages.js @@ -23,7 +23,7 @@ import ro from './ro.json'; import ru from './ru.json'; import sk from './sk.json'; import vi from './vi.json'; -import zh_Hant from './zh-Hant.json'; +import zh_Hant from './zh_Hant.json'; // Refer this for checking the codes and creating new folders https://developer.chrome.com/webstore/i18n diff --git a/app/locales/ml.json b/app/locales/ml.json index 978c954c68..12fe87edd9 100644 --- a/app/locales/ml.json +++ b/app/locales/ml.json @@ -1,4 +1,23 @@ { + "history": { + "no_exposure": "സാധ്യതയില്ല", + "possible_exposure": "സാധ്യത", + "possible_exposure_para": "താങ്കൾ കോവിഡ്‌ പോസിറ്റീവ് ആയ ആരെങ്കിലുമായി ബന്ധപ്പെടുകയോ അടുത്തിടപഴകുകയോ ചെയ്തിട്ടുണ്ടാവാം", + "what_does_this_mean": "എന്താണ് ഇതുകൊണ്ട് അർത്ഥമാക്കുന്നത്?", + "what_does_this_mean_para": "നിങ്ങളുടെ ജി‌പി‌എസ് ചരിത്രത്തെ അടിസ്ഥാനമാക്കി, നിങ്ങൾ‌ COVID-19 രോഗനിർണയം നടത്തിയ ആരുമായും സമ്പർക്കം പുലർത്തുകയോ അല്ലെങ്കിൽ‌ അവരുമായി അടുത്തിടപഴകുകയോ ചെയ്‌തിരിക്കാം. ഇത് നിങ്ങൾ രോഗബാധിതനാണെന്ന് അർത്ഥമാക്കുന്നില്ല, പക്ഷേ നിങ്ങൾ ആയിരിക്കാൻ സാധ്യതയുണ്ട്.\n\n നിങ്ങൾ എന്തുചെയ്യണം എന്നതിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങൾക്ക് മയോ ക്ലിനിക്കിന്റെ വെബ്‌സൈറ്റ് പരിശോധിക്കാം.", + "what_if_no_symptoms": "ഞാൻ രോഗലക്ഷണങ്ങൾ കാണിക്കുന്നില്ലെങ്കിലോ??", + "what_if_no_symptoms_para": "നിങ്ങൾക്ക് രോഗലക്ഷണങ്ങളൊന്നുമില്ലെങ്കിലും പരിശോധന നടത്താൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ നിങ്ങളുടെ അടുത്തുള്ള ടെസ്റ്റിംഗ് സൈറ്റിലേക്ക് പോകാം.\n\nലക്ഷണങ്ങൾ പ്രകടിപ്പിക്കാത്ത വ്യക്തികൾക്ക് ചിലപ്പോൾ അണുബാധയുണ്ടാക്കുകയും മറ്റുള്ളവരെ ബാധിക്കുകയും ചെയ്യാം. സാമൂഹിക അകലം പാലിക്കുന്നതിൽ ശ്രദ്ധാലുവായിരിക്കുക, വലിയ ഗ്രൂപ്പുകളുമായോ അല്ലെങ്കിൽ അപകടസാധ്യതയുള്ള വ്യക്തികളുമായോ (പ്രായമായവർ, മറ്റ് മെഡിക്കൽ പ്രശ്നങ്ങളുള്ളവർ) ബന്ധപ്പെടുന്നതിൽ ശ്രദ്ധാലുവായിരിക്കുക. നിങ്ങളുടെ സ്വന്തം അപകടസാധ്യതയും മറ്റുള്ളവരുടെ അപകടസാധ്യതയും കൈകാര്യം ചെയ്യുന്നതിന് ഇത് പ്രധാനമാണ്." + }, + "import": { + "button_text": "പഴയ ലൊക്കേഷനുകൾ ഇംപോർട്ട് ചെയ്യുക", + "google": { + "disclaimer": "COVID സെയ്ഫ് പാത്ത്-ന് Google മായി ഒരു ബന്ധവുമില്ല കൂടാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും പങ്കിടില്ല.", + "title": "Google ഭൂപടം", + "visit_button_text": "ഗൂഗിൾ ടേക് ഔട്ട് സന്ദർശിക്കുക" + }, + "subtitle": "ഈ അപ്ലിക്കേഷൻ ഡൗൺലോഡുചെയ്യുന്നതിന് മുമ്പ് നിങ്ങൾ COVID-19 ഉള്ള ഒരാളെ നേരിട്ടിട്ടുണ്ടോ എന്നറിയാൻ, നിങ്ങളുടെ വ്യക്തിഗത ലൊക്കേഷൻ ചരിത്രം ഇംപോർട്ട് ചെയ്യാൻ നിങ്ങൾക്ക് കഴിയും.", + "title": "ഇംപോർട്ട് ലൊക്കേഷനുകൾ" + }, "label": { "about_title": "വിവരങ്ങൾ", "authorities_add_button_label": "വിശ്വസനീയമായ ഉറവിടം ചേർക്കുക", @@ -14,12 +33,10 @@ "choose_provider_subtitle": "എക്‌സ്‌പോഷറുകളെക്കുറിച്ച് അറിയാൻ നിങ്ങൾ ഒരു ഹെൽത്ത് അതോറിറ്റിയിലേക്ക് സബ്‌സ്‌ക്രൈബു ചെയ്യേണ്ടതുണ്ട്.", "choose_provider_title": "ആരോഗ്യ അതോറിറ്റി തിരഞ്ഞെടുക്കുക", "commitment": "പ്രതിബദ്ധത", - "commitment_para": " നിങ്ങളുടെ സെയ്ഫ് പാത്ത് ഉപയോഗിക്കുന്ന ആളുകളുമായുള്ള നിങ്ങളുടെ ഇടപെടൽ സുരക്ഷിതമായി രേഖപ്പെടുത്തുകയും പരിശോധിക്കുകയും ചെയ്യുന്നു. നിങ്ങളുടെ സമ്മതമില്ലാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും ഫോൺ വിടുകയില്ല.", + "commitment_para": "നിങ്ങളുടെ സെയ്ഫ് പാത്ത് ഉപയോഗിക്കുന്ന ആളുകളുമായുള്ള നിങ്ങളുടെ ഇടപെടൽ സുരക്ഷിതമായി രേഖപ്പെടുത്തുകയും പരിശോധിക്കുകയും ചെയ്യുന്നു. നിങ്ങളുടെ സമ്മതമില്ലാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും ഫോൺ വിടുകയില്ല.", "default_news_site_name": "സെയ്ഫ് പാത്ത് വാർത്തകൾ", "event_history_subtitle": "ആരോഗ്യ അധികാരികൾ പങ്കിട്ട വിവരങ്ങളെ അടിസ്ഥാനമാക്കി നിങ്ങളുടെ എക്സ്പോഷർ മനസ്സിലാക്കുക.", "event_history_title": "എക്സ്പോഷർ ചരിത്രം", - "export_para_1": "നിങ്ങൾ COVID-19 പോസിറ്റീവ് ആണെന്ന് കണ്ടെത്തിയാൽ, ദയവായി നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം പ്രാദേശിക അധികാരികളുമായി പങ്കിടുക. ഇതിലൂടെ നിങ്ങൾ നിങ്ങളുടെ ഭാഗം ചെയ്യുന്നു .", - "export_para_2": " സമയത്തിന്റെയും സ്ഥലങ്ങളുടെയും ലളിതമായ ലിസ്റ്റായി ലൊക്കേഷൻ പങ്കിടുന്നു, അധിക വിവരങ്ങളൊന്നുമില്ല.", "home_at_risk_header": "You May Be Exposed", "home_at_risk_subsubtext": "നിങ്ങൾ രോഗബാധിതനാണെന്ന് ഇതിനർത്ഥമില്ല", "home_at_risk_subtext": "നിങ്ങളുടെ ജി‌പി‌എസ് ചരിത്രത്തെ അടിസ്ഥാനമാക്കി, നിങ്ങൾ‌ COVID-19 രോഗനിർണയം നടത്തിയ ഒരാളുമായി സമ്പർക്കം പുലർത്തുകയോ അല്ലെങ്കിൽ‌ അവരുമായി അടുത്തിടപഴകുകയോ ചെയ്‌തിരിക്കാം.", @@ -28,20 +45,19 @@ "home_mayo_link_label": "മയോ ക്ലിനിക്കിൽ നിന്ന്", "home_no_contact_header": "അറിയപ്പെടുന്ന കോൺടാക്റ്റുകളൊന്നുമില്ല", "home_no_contact_subtext": "ലഭ്യമായ ഡാറ്റയെ അടിസ്ഥാനമാക്കി നിങ്ങൾ COVID-19 പോസിറ്റീവ് ആണെന്ന് റിപ്പോർട്ടുചെയ്ത ആരുടെയും അടുത്ത് സമ്പർക്കം ഉണ്ടായിട്ടില്ല.", + "home_setting_off_header": "അജ്ഞാതം", + "home_setting_off_subtext": "ക്രമീകരണ സ്ക്രീനിൽ ലൊക്കേഷൻ ചരിത്രം പ്രാപ്‌തമാക്കിയില്ലെങ്കിൽ നിങ്ങൾക്ക് അപകടസാധ്യത ഉണ്ടോ എന്ന് ഞങ്ങൾക്ക് പറയാനാവില്ല.", "home_unknown_header": "അജ്ഞാതം", "home_unknown_subtext": "നിങ്ങളുടെ ലൊക്കേഷൻ ആക്‌സസ്സുചെയ്യാൻ അപ്ലിക്കേഷൻ പ്രാപ്‌തമാക്കിയില്ലെങ്കിൽ നിങ്ങൾക്ക് അപകടസാധ്യതയുണ്ടോ ഞങ്ങൾക്ക് പറയാനാവില്ല.", - "import_step_1":"1. നിങ്ങളുടെ Google Account ലേക്ക് പ്രവേശിച്ച് നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം ഡൗൺലോഡ് ചെയ്യുക", - "import_step_2":"2. ഡൗൺലോഡ് ചെയ്ത ശേഷം, ഈ സ്ക്രീൻ വീണ്ടും തുറക്കുക. ഡാറ്റ ഓട്ടോമാറ്റിയ്ക്കായി ഇംപോർട്ട് ചെയ്യും.", - "import_title":"ഇംപോർട്ട് ലൊക്കേഷനുകൾ", "latest_news": "പുതിയ വാർത്ത", "launch_done_header": "എല്ലാം പൂർത്തിയായി", "launch_done_subheader": "ആരംഭിക്കാൻ നിങ്ങൾ തയ്യാറാണ്. ഓർമ്മിക്കുക, നിങ്ങൾക്ക് എല്ലായ്പ്പോഴും നിങ്ങളുടെ മുൻ‌ഗണനകൾ പിന്നീട് അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയും.", "launch_enable_location": "ലൊക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കുക", "launch_enable_notif": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", "launch_finish_set_up": "സജ്ജീകരണം പൂർത്തിയാക്കുക", - "launch_get_started": "ആരംഭിക്കുക ", + "launch_get_started": "ആരംഭിക്കുക", "launch_location_access": "ലൊക്കേഷൻ ആക്‌സസ്സ്", - "launch_location_header":"നിങ്ങൾ എവിടേക്കാണ് പോകുന്നതെന്ന് ഓർമ്മിക്കാൻ, നിങ്ങളുടെ ഫോൺ നിങ്ങളുടെ ലൊക്കേഷൻ സംരക്ഷിക്കേണ്ടതുണ്ട്.", + "launch_location_header": "നിങ്ങൾ എവിടേക്കാണ് പോകുന്നതെന്ന് ഓർമ്മിക്കാൻ, നിങ്ങളുടെ ഫോൺ നിങ്ങളുടെ ലൊക്കേഷൻ സംരക്ഷിക്കേണ്ടതുണ്ട്.", "launch_location_subheader": "വിഷമിക്കേണ്ട, നിങ്ങളുടെ സമ്മതമില്ലാതെ ഡാറ്റ വിവരങ്ങൾ ഒരിക്കലും നിങ്ങളുടെ ഫോണിൽ നിന്ന് പുറത്തുപോകില്ല.", "launch_next": "അടുത്തത്", "launch_notif_header": "നിങ്ങൾ ഒരു രോഗബാധിതനുമായി സമ്പർക്കം പുലർത്തുന്നുവെങ്കിൽ അറിയിപ്പുകൾ ലഭിക്കും", @@ -56,55 +72,42 @@ "launch_screen4_subheader": "നിങ്ങൾ പോസിറ്റീവ് ആണെങ്കിൽ, വിവരങ്ങൾ പങ്കിടണോ എന്ന് നിങ്ങൾക്ക് മാത്രമേ തിരഞ്ഞെടുക്കാനാകൂ.", "launch_set_up_phone": "എന്റെ ഫോൺ സജ്ജമാക്കുക", "legal_page_title": "നിയമപരമായ വിവരങ്ങൾ", - "less_than_one_minute": "1 മിനിറ്റിൽ താഴെ", "loading_public_data": "ഡാറ്റ ലോഡുചെയ്യുന്നു ...", "location_disabled_message": "COVID സെയ്ഫ് പാത്ത്-ന് ലൊക്കേഷൻ സേവനങ്ങൾ ആവശ്യമാണ്.", "location_disabled_title": "ലൊക്കേഷൻ ട്രാക്കിംഗ് പ്രവർത്തനരഹിതമാക്കി.", "location_enabled_message": "ഈ ഫോണിൽ അഞ്ച് മിനിറ്റിലൊരിക്കൽ നിങ്ങളുടെ ജിപിഎസ് കോർഡിനേറ്റുകൾ COVID സെയ്ഫ് പാത്ത് സുരക്ഷിതമായി സംരക്ഷിക്കുന്നു.", - "location_enabled_title": " COVID സെയ്ഫ് പാത്ത് പ്രവർത്തനക്ഷമമാക്കി", - "maps_import_button_text": "പഴയ ലൊക്കേഷനുകൾ ഇംപോർട്ട് ചെയ്യുക", - "maps_import_disclaimer": "COVID സെയ്ഫ് പാത്ത്-ന് Google മായി ഒരു ബന്ധവുമില്ല കൂടാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും പങ്കിടില്ല.", - "maps_import_text": "ഈ അപ്ലിക്കേഷൻ ഡൗൺലോഡുചെയ്യുന്നതിന് മുമ്പ് നിങ്ങൾ COVID-19 ഉള്ള ഒരാളെ നേരിട്ടിട്ടുണ്ടോ എന്നറിയാൻ, നിങ്ങളുടെ വ്യക്തിഗത ലൊക്കേഷൻ ചരിത്രം ഇംപോർട്ട് ചെയ്യാൻ നിങ്ങൾക്ക് കഴിയും.", - "maps_import_title": "Google ഭൂപടം", - "nCoV2019_url_info": "ഈ മാപ്പിനായുള്ള ഡാറ്റാസെറ്റിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക്", + "location_enabled_title": "COVID സെയ്ഫ് പാത്ത് പ്രവർത്തനക്ഷമമാക്കി", + "logging_active": "ലൊക്കേഷൻ സജീവമാണ്", + "logging_inactive": "ലൊക്കേഷൻ നിഷ്‌ക്രിയമാണ്", "news_subtitle": "നിങ്ങളുടെ ആരോഗ്യ അതോറിറ്റിയിൽ നിന്നുള്ള പൊതുവായ ഏറ്റവും പുതിയ COVID അപ്‌ഡേറ്റുകളെക്കുറിച്ച് വായിക്കുക.", "news_title": "പുതിയ വാർത്ത", "no_data": "ഡാറ്റാ ഇല്ല", - "notification_2_weeks_ago": "2 ആഴ്ച മുമ്പ്", - "notification_data_not_available": "എക്‌സ്‌പോഷർ ഡാറ്റയൊന്നും ലഭ്യമല്ല.", - "notification_select_authority": "ഹെൽത്ത് കെയർ അതോറിറ്റി തിരഞ്ഞെടുക്കുക", - "notification_title": "എക്സ്പോഷർ പ്രൊഫൈൽ 2 ആഴ്ച ", - "notification_today": "ഇന്ന്", - "notification_warning_text": "നിങ്ങളുടെ പ്രദേശത്ത് ഒരു ഹെൽത്ത് കെയർ അതോറിറ്റി നിലവിലുണ്ടെങ്കിൽ എക്സ്പോഷർ അപകടസാധ്യതകളുടെ പതിവ് അപ്‌ഡേറ്റുകൾ നേടുന്നതിന് നിങ്ങൾക്ക് സബ്‌സ്‌ക്രൈബുചെയ്യാനാകും.", - "notifications_exposure_format": "{{daysAgo}} ദിവസങ്ങൾക്ക് മുമ്പ് നിങ്ങൾ അണുബാധയുള്ള ഒരാളെ {{exposureTime}} മിനിറ്റ് സമ്പർക്കം പുലര്‍ത്തി .", - "notifications_exposure_format_today": "ഇന്ന് നിങ്ങൾ അണുബാധയുള്ള ഒരാളെ {{exposureTime}} മിനിറ്റ് സമ്പർക്കം പുലര്‍ത്തി.", - "notifications_exposure_format_yesterday": "ഇന്നലെ നിങ്ങൾ അണുബാധയുള്ള ഒരാളെ {{exposureTime}} മിനിറ്റ് സമ്പർക്കം പുലര്‍ത്തി.", - "notifications_no_exposure": "കഴിഞ്ഞ രണ്ടാഴ്ചയ്ക്കിടെ COVID-19 എക്സ്പോഷറുകളൊന്നുമില്ല.", - "overlap_found_button_label": "പൊതു ഡാറ്റ ലോഡുചെയ്‌തു", - "overlap_no_results_button_label": "പൊതു ഡാറ്റ ലോഡുചെയ്‌തു", - "overlap_para_1": "പച്ച നടപ്പാത നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രത്തെ പ്രതിനിധീകരിക്കുന്നു\n\nഇളം പർപ്പിൾ സർക്കിളുകൾ പൊതു ഡാറ്റാഗണത്തെ പ്രതിനിധീകരിക്കുന്നു", - "overlap_title": "ഓവർലാപ്പ് പരിശോധിക്കുക", "push_at_risk_message": "നിങ്ങൾ ഒരു COVID-19 രോഗിയുമായി സമ്പർക്കം പുലര്‍ത്തി", "push_at_risk_title": "നിങ്ങൾക്ക് അപകടസാധ്യതയുണ്ട്", "see_exposure_history": "എക്‌സ്‌പോഷർ ചരിത്രം കാണുക", "settings_title": "ഡാഷ്ബോർഡ്", - "share_location_data": "ലൊക്കേഷൻ ഡാറ്റ പങ്കിടുക", - "show_overlap": "പൊതു ഡാറ്റാസെറ്റ് കാണാൻ ക്ലിക്കുചെയ്യുക", "team": "ടീം", "team_para": "എപ്പിഡെമിയോളജിസ്റ്റുകൾ, എഞ്ചിനീയർമാർ, ഡാറ്റാ ശാസ്ത്രജ്ഞർ, ഡിജിറ്റൽ സ്വകാര്യതാ സുവിശേഷകർ, പ്രൊഫസർമാർ, പ്രശസ്ത സ്ഥാപനങ്ങളിൽ നിന്നുള്ള ഗവേഷകർ എന്നിവരുടെ ഒരു കൺസോർഷ്യം ഉൾപ്പെടുന്നതാണ് ഞങ്ങളുടെ ടീം: എംഐടി, ഹാർവാർഡ്, മയോ ക്ലിനിക്, ട്രിപ്പിൾബ്ലൈൻഡ്, ഐനെട്ര, ഏണസ്റ്റ് & യംഗ്, ലിങ്ക് സംരംഭം.", - "terms_of_use": "നിബന്ധനകൾ", - "tested_positive_subtitle": "നിങ്ങളുടെ സ്വകാര്യ ഡാറ്റ ആരോഗ്യ അധികാരികൾക്ക് കൈമാറാനോ ബാക്കപ്പ് ചെയ്യാനോ അല്ലെങ്കിൽ പങ്കിടാനോ കഴിയും.", - "tested_positive_title": "ലൊക്കേഷൻ ചരിത്രം പങ്കിടുക." + "terms_of_use": "നിബന്ധനകൾ" }, - "history": { - "no_exposure": "എക്സ്പോഷർ ഇല്ല", - "possible_exposure_para": "താങ്കൾ കോവിഡ്‌ പോസിറ്റീവ് ആയ ആരെങ്കിലുമായി ബന്ധപ്പെടുകയോ അടുത്തിടപഴകുകയോ ചെയ്തിട്ടുണ്ടാവാം", - "possible_exposure": "സാധ്യമായ എക്‌സ്‌പോഷർ", - "timeline": "ടൈംലൈൻ", - "what_does_this_mean_para": "നിങ്ങളുടെ ജി‌പി‌എസ് ചരിത്രത്തെ അടിസ്ഥാനമാക്കി, നിങ്ങൾ‌ COVID-19 രോഗനിർണയം നടത്തിയ ആരുമായും സമ്പർക്കം പുലർത്തുകയോ അല്ലെങ്കിൽ‌ അവരുമായി അടുത്തിടപഴകുകയോ ചെയ്‌തിരിക്കാം. ഇത് നിങ്ങൾ രോഗബാധിതനാണെന്ന് അർത്ഥമാക്കുന്നില്ല, പക്ഷേ നിങ്ങൾ ആയിരിക്കാൻ സാധ്യതയുണ്ട്.\n\n നിങ്ങൾ എന്തുചെയ്യണം എന്നതിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങൾക്ക് മയോ ക്ലിനിക്കിന്റെ വെബ്‌സൈറ്റ് പരിശോധിക്കാം.", - "what_does_this_mean": "എന്താണ് ഇതുകൊണ്ട് അർത്ഥമാക്കുന്നത്?", - "what_if_no_symptoms_para": "നിങ്ങൾക്ക് രോഗലക്ഷണങ്ങളൊന്നുമില്ലെങ്കിലും പരിശോധന നടത്താൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ നിങ്ങളുടെ അടുത്തുള്ള ടെസ്റ്റിംഗ് സൈറ്റിലേക്ക് പോകാം.\n\nലക്ഷണങ്ങൾ പ്രകടിപ്പിക്കാത്ത വ്യക്തികൾക്ക് ചിലപ്പോൾ അണുബാധയുണ്ടാക്കുകയും മറ്റുള്ളവരെ ബാധിക്കുകയും ചെയ്യാം. സാമൂഹിക അകലം പാലിക്കുന്നതിൽ ശ്രദ്ധാലുവായിരിക്കുക, വലിയ ഗ്രൂപ്പുകളുമായോ അല്ലെങ്കിൽ അപകടസാധ്യതയുള്ള വ്യക്തികളുമായോ (പ്രായമായവർ, മറ്റ് മെഡിക്കൽ പ്രശ്നങ്ങളുള്ളവർ) ബന്ധപ്പെടുന്നതിൽ ശ്രദ്ധാലുവായിരിക്കുക. നിങ്ങളുടെ സ്വന്തം അപകടസാധ്യതയും മറ്റുള്ളവരുടെ അപകടസാധ്യതയും കൈകാര്യം ചെയ്യുന്നതിന് ഇത് പ്രധാനമാണ്.", - "what_if_no_symptoms": "ഞാൻ രോഗലക്ഷണങ്ങൾ കാണിക്കുന്നില്ലെങ്കിലോ??" - + "onboarding": { + "eula_checkbox": "ലൈസൻസിംഗ് കരാർ ഞാൻ അംഗീകരിക്കുന്നു", + "eula_continue": "തുടരുക", + "eula_message": "*Safe Paths ഉപയോഗിക്കുന്നതിന് നിങ്ങൾ അംഗീകരിക്കണം" + }, + "share": { + "button_text": "ലൊക്കേഷൻ ഡാറ്റ പങ്കിടുക", + "paragraph_first": "നിങ്ങൾ COVID-19 പോസിറ്റീവ് ആണെന്ന് കണ്ടെത്തിയാൽ, ദയവായി നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം പ്രാദേശിക അധികാരികളുമായി പങ്കിടുക. ഇതിലൂടെ നിങ്ങൾ നിങ്ങളുടെ ഭാഗം ചെയ്യുന്നു .", + "paragraph_second": " സമയത്തിന്റെയും സ്ഥലങ്ങളുടെയും ലളിതമായ ലിസ്റ്റായി ലൊക്കേഷൻ പങ്കിടുന്നു, അധിക വിവരങ്ങളൊന്നുമില്ല.", + "subtitle": "നിങ്ങളുടെ സ്വകാര്യ ഡാറ്റ ആരോഗ്യ അധികാരികൾക്ക് കൈമാറാനോ ബാക്കപ്പ് ചെയ്യാനോ അല്ലെങ്കിൽ പങ്കിടാനോ കഴിയും.", + "title": "ലൊക്കേഷൻ ചരിത്രം പങ്കിടുക." + }, + "version_update": { + "alert_label": "COVID Safe Paths കാലഹരണപ്പെട്ടതാണ്", + "alert_sublabel": "ഒരു പുതിയ പതിപ്പിലേക്ക് അപ്‌ഡേറ്റ് ചെയ്യണോ?", + "later": "പിന്നീട്", + "push_notification_message": "നിങ്ങളുടെ അപ്ലിക്കേഷൻ അപ്‌ഡേറ്റ് ചെയ്യുക", + "push_notification_title": "അപ്ലിക്കേഷൻ കാലഹരണപ്പെട്ടതാണ്", + "update": "അപ്ഡേറ്റ്" } } diff --git a/app/locales/nl.json b/app/locales/nl.json index 78d8f888c9..8b286f4bde 100644 --- a/app/locales/nl.json +++ b/app/locales/nl.json @@ -1,17 +1,27 @@ { "history": { "no_exposure": "Geen blootstelling", - "possible_exposure": "Mogelijke blootstelling", + "possible_exposure": "Mogelijk", "possible_exposure_para": "Het is mogelijk dat u contact heeft gehad met of in de buurt bent geweest met iemand die positief heeft getest op COVID-19", "what_does_this_mean": "Wat betekent dit?", - "what_does_this_mean_para": "Op basis van uw GPS-geschiedenis blijkt dat u mogelijk in contact bent geweest met iemand met de diagnose COVID-19. Het kan dat u niet geïnfecteerd bent, maar dit is niet zeker. Voor verdere informatie kunt u het beste naar de website van het RIVM.", - "what_if_no_symptoms": "Wat als ik geen symptonen vertoon?", - "what_if_no_symptoms_para": "Personen die geen symptomen vertonen, kunnen soms nog steeds de infectie dragen en anderen infecteren. Wees voorzichtig met sociale zaken en neem altijd 1.5 meter afstand. Wees extra voorzichtig met individuen met een risico (ouderen en personnen met andere medische problemen). Het is belangrijk om zowel uw risico als het risico voor anderen te beperken. Voor meer informatie kunt u naar de website van het RIVM." + "what_does_this_mean_para": "Uit uw GPS-geschiedenis blijkt dat u mogelijk in contact bent geweest met iemand met de diagnose COVID-19. Het kan dat u niet geïnfecteerd bent, maar dit is niet zeker. Voor verdere informatie kunt u het beste naar de website van het RIVM.", + "what_if_no_symptoms": "Wat als ik geen symptomen vertoon?", + "what_if_no_symptoms_para": "Personen die geen symptomen vertonen, kunnen soms nog steeds de infectie dragen en anderen infecteren. Wees voorzichtig met sociale zaken en neem altijd 1.5 meter afstand. Wees extra voorzichtig met individuen met een risico (ouderen en personen met andere medische problemen). Het is belangrijk om zowel uw risico als het risico voor anderen te beperken. Voor meer informatie kunt u naar de website van het RIVM." + }, + "import": { + "button_text": "Importeer eerdere locaties", + "google": { + "disclaimer": "COVID Safe Paths is niet aangesloten bij Google en zal nooit uw data delen.", + "title": "Google Maps", + "visit_button_text": "Ga naar Google Takeout" + }, + "subtitle": "Importeer uw locatie geschiedenis om te zien of u in contact bent geweest met een COVID-19 besmet persoon, voordat u deze applicatie gebruikte.", + "title": "Importeer locaties" }, "label": { "about_title": "Informatie", "authorities_add_button_label": "Voeg een vertrouwde bron toe", - "authorities_add_url": "Voeg authoriteit toe via URL", + "authorities_add_url": "Voeg autoriteit toe via URL", "authorities_desc": "Kies vertrouwde gezondheidsinstanties in uw regio om blootstellingsgegevens te verkrijgen. Selecteer een naam uit het wereldwijde register of voer het webadres in dat is verstrekt door een autoriteit die Safe Paths heeft geïmplementeerd.", "authorities_input_placeholder": "Plak hier uw URL", "authorities_no_sources": "Nog geen gegevensbron", @@ -20,77 +30,82 @@ "authorities_removal_alert_proceed": "Ga verder", "authorities_removal_alert_title": "Verwijder autoriteit", "authorities_title": "Vertrouwde bronnen", - "choose_provider_subtitle": "Om op de hoogte te blijven van mogelijke blootstellingen, kunt u het best abboneren op een gezondheidsinstantie", + "choose_provider_subtitle": "Om op de hoogte te blijven van mogelijke blootstellingen, kunt u het best abonneren op een gezondheidsinstantie.", "choose_provider_title": "Kies een gezondheidsinstantie", "commitment": "Onze belofte", - "commitment_para": "Safe Paths registreert en controleert uw interactie met andere mensen via uw locatie. Uw gegevens zullen NOOIT uw telefoon verlaten zonder uw explicite toestemming", - "default_news_site_name": "Safe Paths Nieuws", - "event_history_subtitle": "Herken uw persoonlijke blootstelling aan de hand van informatie van gezondheidsinstanties.", + "commitment_para": "Safe Paths registreert en controleert uw interactie met andere mensen via uw locatie. Uw gegevens zullen nooit uw telefoon verlaten zonder uw nadrukkelijke toestemming.", + "default_news_site_name": "Safe Paths nieuws", + "event_history_subtitle": "Herken uw persoonlijke blootstelling met informatie van gezondheidsinstanties.", "event_history_title": "Blootstelling geschiedenis", - "export_para_1": "Als u positief bent getest voor COVID-19, draag dan uw steentje bij door uw locatiegeschiedenis te delen met lokale autoriteiten.", - "export_para_2": "Uw locatie wordt gedeeld in een eenvoudige lijst met alleen de tijden en plaatsen, zonder additionele informatie.", "home_at_risk_header": "U wordt mogelijk blootgesteld", "home_at_risk_subsubtext": "Dit betekent echter niet dat u besmet bent.", - "home_at_risk_subtext": "Op basis van uw locatiegeschiedenis blijkt dat u in de buurt was of contact had met iemand met de diagnose COVID-19.", + "home_at_risk_subtext": "Uit uw locatiegeschiedenis blijkt dat u in de buurt was of contact had met iemand met de diagnose COVID-19.", "home_enable_location": "Locatiegegevens inschakelen", "home_mayo_link_heading": "Meer COVID-19 informatie", "home_mayo_link_label": "van de Mayo Clinic (Verenigde Staten)", "home_no_contact_header": "Geen bekend contact", "home_no_contact_subtext": "Op basis van beschikbare gegevens bent u nog nooit in de buurt van iemand geweest met de diagnose COVID-19.", "home_setting_off_header": "Onbekend", - "home_setting_off_subtext": "Wij kunnen niet zeggen of u risico loopt, tenzij u de app toegang geeft tot uw locatie op het instellingen scherm.", + "home_setting_off_subtext": "We kunnen niet zeggen of u risico loopt, tenzij u de app toegang geeft tot uw locatie op het instellingen scherm.", "home_unknown_header": "Onbekend", - "home_unknown_subtext": "Wij kunnen niet zeggen of u risico loopt, tenzij u de app toegang geeft tot uw locatie.", - "import_step_1": "Uw locatiegeschiedenis importeren van Google geeft u een voorsprong op het toevoegen van recente locatie gegevens.", - "import_step_2": "Voordat u kan importeren, moet u eerst uw data importeren via 'Google Takeout'.", - "import_step_3": "Bezoek 'Google Takeout' en exporteer uw locatiegeschiedenis volgens de deze instellingen: \n1. Delivery method: \"Add to Drive\" \n2. Frequency: \"Export once\" \n3. File type & size: \".zip\" and \"1GB\" \n4. Google verstuurd daarna een email waarneer de export klaar is \n5. Keer terug naar Safe Paths om hier de locaties te importeren. Importeer opties: \n- Importeer vanuit Google Drive \n- Download vanuit uw browser en daarna vanuit uw bestanden. Wees verbonden met de WiFi, omdat de bestanden groot kunnen zijn.", - "import_takeout": "Ga naar Google Takeout", - "import_title": "Importeer locaties", + "home_unknown_subtext": "We kunnen niet zeggen of u risico loopt, tenzij u de app toegang geeft tot uw locatie.", "latest_news": "Laatste nieuws", "launch_done_header": "Klaar voor gebruik", - "launch_done_subheader": "U kan altijd uw instellingen op een later moment nog kan aanpassen.", + "launch_done_subheader": "U kunt altijd uw instellingen op een later moment nog kan aanpassen.", "launch_enable_location": "Locatie inschakelen", "launch_enable_notif": "Meldingen inschakelen", "launch_finish_set_up": "Installatie voltooien", "launch_get_started": "Aan de slag", - "launch_location_access": "Locatietoegang", + "launch_location_access": "Locatie toegang", "launch_location_header": "Om te onthouden waar u naar toe gaat, moet uw mobiel uw locatie opslaan.", - "launch_location_subheader": "Geen zorgen, de informatie verlaat nooit uw telefoon zonder uw explicite toestemming", + "launch_location_subheader": "Geen zorgen, de informatie verlaat nooit uw telefoon zonder uw nadrukkelijke toestemming.", "launch_next": "Volgende", - "launch_notif_header": "Meldingen waarschuwen u als u in contact bent geweest met een besmet persoon", - "launch_notif_subheader": "Wij zullen u nooit lastig vallen. Alleen voor meldingen voor potentiele blootstelling.", + "launch_notif_header": "Meldingen waarschuwen u als u in contact bent geweest met een besmet persoon.", + "launch_notif_subheader": "We zullen u nooit lastig vallen. Alleen voor meldingen voor potentiële blootstelling.", "launch_notification_access": "Meldingen activeren", "launch_screen1_header": "Terugkeren naar normaal start hier.", "launch_screen2_header": "Ontvang meldingen als u iemand tegen bent gekomen met de diagnose COVID-19.", "launch_screen2_subheader": "Kennis is macht.", "launch_screen3_header": "Als u ook positief test op COVID-19, kunt u ervoor kiezen om uw gegevens anoniem te doneren.", "launch_screen3_subheader": "Dit helpt iedereen om u heen om veilig te blijven.", - "launch_screen4_header": "U heeft de volledige controle. Gegevens worden alleen op uw telefoon opgeslagen", + "launch_screen4_header": "U heeft de volledige controle. Gegevens worden alleen op uw telefoon opgeslagen.", "launch_screen4_subheader": "Als u positief test op COVID-19, kan alleen u ervoor kiezen om uw gegevens anoniem te doneren.", "launch_set_up_phone": "Stel mijn telefoon in", "legal_page_title": "Juridisch", "loading_public_data": "Gegevens worden geladen ...", "location_disabled_message": "COVID Safe Paths vereist locatie diensten.", "location_disabled_title": "Het traceren van locatie is uitgeschakeld", - "location_enabled_message": "COVID Safe Paths slaat uw GPS coördinaten eens in de vijf minuten veilig op dit apparaat op.", + "location_enabled_message": "COVID Safe Paths slaat uw GPS-coördinaten eens in de vijf minuten veilig op dit apparaat op.", "location_enabled_title": "COVID Safe Paths ingeschakeld", "logging_active": "Locatie actief", "logging_inactive": "Locatie inactief", - "maps_import_button_text": "Importeer eerdere locaties", - "maps_import_disclaimer": "COVID Safe Paths is niet aangesloten bij Google en zal nooit uw data delen.", - "maps_import_text": "Om te zien of u in contact bent geweest met een COVID-19 besmet persoon, voordat u deze applicatie gebruikte, importeer uw locatie geschiedenis.", - "maps_import_title": "Google Maps", - "news_subtitle": "Lees meer over de laatste COVID-19 nieuws op de website vaRead about the latest COVID updates from your health authority and in general.", + "news_subtitle": "Lees meer over de laatste COVID-19 nieuws op de website van uw gezondheidsinstantie.", "news_title": "Laatste nieuws", "no_data": "Geen gegevens", - "push_at_risk_message": "U bent in de buurt geeest met iemand met de diagnose COVID-19.", + "push_at_risk_message": "U bent in de buurt geweest met iemand met de diagnose COVID-19", "push_at_risk_title": "U loopt mogelijk risico", - "see_exposure_history": "", + "see_exposure_history": "Bekijk blootstelling geschiedenis", "settings_title": "Dashboard", - "share_location_data": "Deel uw locatie gegevens", "team": "Team", "team_para": "Ons team bestaat uit een consortium van epidemiologen, ingenieurs, data wetenschappers, digitale privacy-evangelisten, professoren en onderzoekers van gerenommeerde instellingen, waaronder: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young en Link Ventures.", - "terms_of_use": "Gebruiksvoorwaarden", - "tested_positive_title": "Deel locatie geschiedenis" + "terms_of_use": "Gebruiksvoorwaarden" + }, + "onboarding": { + "eula_checkbox": "Ik accepteer de licentieovereenkomst", + "eula_continue": "Ga verder", + "eula_message": "U moet accepteren om Safe Paths te gebruiken" + }, + "share": { + "button_text": "Deel uw locatie gegevens", + "subtitle": "Uw privégegevens kunnen worden overgedragen aan gezondheidsautoriteiten, een back-up worden gemaakt of anderszins worden gedeeld.", + "title": "Deel locatie geschiedenis" + }, + "version_update": { + "alert_label": "COVID Safe Paths is verouderd", + "alert_sublabel": "Update naar een nieuwe versie?", + "later": "Later", + "push_notification_message": "Update uw applicatie", + "push_notification_title": "Applicatie is verouderd", + "update": "Update" } } diff --git a/app/locales/pt_BR.json b/app/locales/pt_BR.json index 1c57fe16d5..8218e5eb97 100644 --- a/app/locales/pt_BR.json +++ b/app/locales/pt_BR.json @@ -1,12 +1,22 @@ { "history": { "no_exposure": "Sem exposição conhecida", - "possible_exposure_para": "É possível que você tenha tido proximidade ou contato com uma pessoa que testou positivo para COVID-19", "possible_exposure": "Exposição possível", - "what_does_this_mean_para": "Baseado no seu histórico de localização por GPS, é possível que você tenha estado próximo ou em contato com alguém diagnosticado com COVID-19. Isso não significa, necessariamente, que você tenha se infectado, apesar de ser possível.\n\nPara mais orientações, consulte o site da Mayo Clinic (site em inglês).", + "possible_exposure_para": "É possível que você tenha tido proximidade ou contato com uma pessoa que testou positivo para COVID-19", "what_does_this_mean": "O que isso significa?", - "what_if_no_symptoms_para": "Caso queira fazer um teste, você pode verificar a disponibilidade em sua região para testes em pessoas assintomáticas.\n\nPessoas sem sintomas também podem transmitir a doença. Cuidados quanto ao distanciamento social, evitar aglomerações ou contato com grupos de risco (idosos e aqueles com problemas de saúde) são importantes para diminuir o seu risco e o dos outros.", - "what_if_no_symptoms": "E se eu não tiver sintomas?" + "what_does_this_mean_para": "Baseado no seu histórico de localização por GPS, é possível que você tenha estado próximo ou em contato com alguém diagnosticado com COVID-19. Isso não significa, necessariamente, que você tenha se infectado, apesar de ser possível.\n\nPara mais orientações, consulte o site da Mayo Clinic (site em inglês).", + "what_if_no_symptoms": "E se eu não tiver sintomas?", + "what_if_no_symptoms_para": "Caso queira fazer um teste, você pode verificar a disponibilidade em sua região para testes em pessoas assintomáticas.\n\nPessoas sem sintomas também podem transmitir a doença. Cuidados quanto ao distanciamento social, evitar aglomerações ou contato com grupos de risco (idosos e aqueles com problemas de saúde) são importantes para diminuir o seu risco e o dos outros." + }, + "import": { + "button_text": "Importar localizações anteriores", + "google": { + "disclaimer": "Safe Paths não tem ligação com Google e nunca compartilha os seus dados.", + "title": "Google Maps", + "visit_button_text": "Visite Google Takeout" + }, + "subtitle": "Para saber se você encontrou com alguém com COVID-19 antes de instalar esse aplicativo, você pode importar seu histórico de localizações pessoais.", + "title": "Importar Localizações" }, "label": { "about_title": "Sobre", @@ -27,8 +37,6 @@ "default_news_site_name": "Notícias Safe Paths", "event_history_subtitle": "Entenda a sua exposição pessoal a partir de informações compartilhadas com as autoridades de saúde.", "event_history_title": "Histórico de exposição", - "export_para_1": "Se você testou positivo para COVID-19, faça a sua parte, compartilhe o seu histórico de localização com as autoridades locais de saúde.", - "export_para_2": "O histórico de localização é uma simples lista de locais e horários, sem compartilhamento de outras informações.", "home_at_risk_header": "Você pode estar exposto.", "home_at_risk_subsubtext": "Isso não quer dizer que você está infectado.", "home_at_risk_subtext": "Baseado no seu histórico de localização por GPS, é possível que você tenha estado próximo ou em contato com alguém com COVID-19.", @@ -41,11 +49,6 @@ "home_setting_off_subtext": "Não é possível verificar se você está em risco se não permitir o uso do histórico de localização nas configurações.", "home_unknown_header": "Desconhecido", "home_unknown_subtext": "Não é possível verificar se você está em risco se não permitir que o aplicativo acesse a sua localização.", - "import_step_1": "Adicionar dados de localização do Google irá te ajudar a construir seu histórico recente de localização.", - "import_step_2": "Para importar, você precisa primeiro exportar os seus dados de localização do Google.", - "import_step_3": "Visite Google Takeout e exporte seu Histórico de Localização com a seguinte configuração: \n1. Método de envio: \"Adicionar ao Google Drive\" \n2. Frequência: \"Exportar uma vez\" \n3. Tipo e tamanho do arquivo: \".zip\" e \"1GB\" \n4. Google enviará um email quando a exportação for concluída \n5. Retorne aqui para importar localizações. Opções de importação: \n- Importe do Google Drive \n- Baixe do navegador para os arquivos locais do celular, e então importe. Deve estar conectado em WiFi já que os arquivos podem ser grandes.", - "import_takeout": "Visite Google Takeout", - "import_title": "Importar Localizações", "latest_news": "Últimas Notícias", "launch_done_header": "Tudo pronto", "launch_done_subheader": "Você está pronto para começar. Lembre-se, você sempre poderá alterar suas preferências, quando quiser.", @@ -76,10 +79,6 @@ "location_enabled_title": "COVID Safe Paths Habilitado", "logging_active": "Localização Ativa", "logging_inactive": "Localização Inativa", - "maps_import_button_text": "Importar localizações anteriores", - "maps_import_disclaimer": "Safe Paths não tem ligação com Google e nunca compartilha os seus dados.", - "maps_import_text": "Para saber se você encontrou com alguém com COVID-19 antes de instalar esse aplicativo, você pode importar seu histórico de localizações pessoais.", - "maps_import_title": "Google Maps", "news_subtitle": "Leia as últimas atualizações sobre COVID de sua autoridade de saúde e em geral.", "news_title": "Últimas notícias", "no_data": "Sem dados", @@ -87,11 +86,13 @@ "push_at_risk_title": "Você pode estar em risco", "see_exposure_history": "Veja seu histórico de exposição", "settings_title": "Painel de controle", - "share_location_data": "Compartilhar dados de localização", "team": "Equipe", "team_para": "Nossa equipe é formada por epidemiologistas, engenheiros, cientistas de dados, especialistas em privacidade digital, professores e pesquisadores de instituições renomadas, como: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young, e Link Ventures.", - "terms_of_use": "Termos de uso", - "tested_positive_subtitle": "Seus dados privados podem ser transferidos para autoridades de saúde, salvos em backup, ou compartilhados.", - "tested_positive_title": "Compartilhar histórico de localização" + "terms_of_use": "Termos de uso" + }, + "share": { + "button_text": "Compartilhar dados de localização", + "subtitle": "Seus dados privados podem ser transferidos para autoridades de saúde, salvos em backup, ou compartilhados.", + "title": "Compartilhar histórico de localização" } } diff --git a/app/locales/pull.sh b/app/locales/pull.sh new file mode 100755 index 0000000000..e7ca82180d --- /dev/null +++ b/app/locales/pull.sh @@ -0,0 +1,65 @@ +# Usage: +# - Create a READ ONLY a token at https://app.lokalise.com/profile +# - Run the command from the root of the project with: +# LOKALISE_TOKEN= yarn i18n:pull + +#!/bin/bash +set -e + +function found_exe() { + hash "$1" 2>/dev/null +} + +if ! found_exe lokalise2; then + if [[ "$OSTYPE" == "darwin"* ]]; then + + if ! found_exe brew; then + echo "You must install homebrew: https://docs.brew.sh/Installation" + exit 1 + fi + brew tap lokalise/cli-2 + brew install lokalise2 + + else + curl -sfL https://raw.githubusercontent.com/lokalise/lokalise-cli-2-go/master/install.sh | sh + fi +fi + +echo "Downloading iOS *.strings" +lokalise2 file download \ + --add-newline-eof \ + --export-empty-as skip \ + --format strings \ + --include-description \ + --original-filenames \ + --unzip-to=ios \ + --export-sort=a_z \ + --config .lokalise.yml --token=$LOKALISE_TOKEN + +echo "Downloading Android strings.xml" +lokalise2 file download \ + --add-newline-eof \ + --export-empty-as skip \ + --format xml \ + --include-description \ + --original-filenames \ + --unzip-to=android/app/src/main/res \ + --export-sort=a_z \ + --config .lokalise.yml --token=$LOKALISE_TOKEN + +echo "Downloading i18next *.json files" +lokalise2 file download \ + --add-newline-eof \ + --export-empty-as=skip \ + --format json \ + --include-description \ + --plural-format=i18next \ + --placeholder-format=i18n \ + --export-sort=a_z \ + --replace-breaks=false \ + --bundle-structure "locales/%LANG_ISO%.json" \ + --original-filenames=false \ + --unzip-to=app \ + --indentation=2sp \ + --json-unescaped-slashes \ + --config .lokalise.yml --token=$LOKALISE_TOKEN diff --git a/app/locales/ro.json b/app/locales/ro.json index bf56c42ee0..71afe2a43f 100644 --- a/app/locales/ro.json +++ b/app/locales/ro.json @@ -8,6 +8,15 @@ "what_if_no_symptoms": "Ce fac în cazul în care nu am simptome COVID-19?", "what_if_no_symptoms_para": "Dacă nu aveți simptome nu înseamna ca nu suneți un posibil purtator de virus. \n\nȘi persoanele care nu prezintă simptome pot fi purtători și pot infecta alte persoane. Din acest motiv, se recomandă distantarea socială și evitarea intrării în contact cu grupuri mari sau cu persoane cu grad mare de risc (persoane în varstă sau cu alte probleme grave de sănătate) pentru a micsora posibilitatea de a infecta alte persoane." }, + "import": { + "button_text": "Importa istoric locații", + "google": { + "disclaimer": "Safe Paths nu are nici o afiliere cu Google și nu dezvaluie NICIODATĂ datele dumneavoastră.", + "title": "Hărți Google" + }, + "subtitle": "Pentru a verifica dacă v-ați intersectat cu cineva găsit pozitiv COVID-19 înainte de a fi descarcat această aplicație, puteți importa istoricul personal al locațiilor dumneavoastră.", + "title": "Importa Istoric" + }, "label": { "about_title": "Despre", "authorities_add_button_label": "Adaugă Sursa de Încredere", @@ -27,8 +36,6 @@ "default_news_site_name": "Știri Safe Paths", "event_history_subtitle": "Familiarizati-vă cu expunerile dumneavoastră, luând în calcul informațiile puse la dispoziție de autoritațile de sănătate", "event_history_title": "Istoricul expunerilor", - "export_para_1": "Dacă sunteți diagnosticat pozitiv cu COVID-19, vă rugăm să împărțiți istoricul traseelor pentru a ajuta la identificarea persoanelor ce au fost posibil infectate de dumneavoastră,", - "export_para_2": "Traseele sunt exportate ca o listă simplă ce conține latitudine, longitudine și timpi, fără nici o altă informație de natură personală.", "home_at_risk_header": "Expunere Posibilă", "home_at_risk_subsubtext": "Atentie - nu înseamnă ca sunteți sigur infectat,", "home_at_risk_subtext": "Având în vedere istoricul dumneavoastră GPS, este posibil să vă fi intersectat cu cineva găsit pozitiv cu COVID-19.", @@ -41,9 +48,6 @@ "home_setting_off_subtext": "Nu se poate estima dacă aveți risc de expunere dacă nu activați istoricul locațiilor in configurația aplicației.", "home_unknown_header": "Necunoscut", "home_unknown_subtext": "Nu se poate estima dacă aveți risc de expunere dacă nu permiteți aplicației sa acceseze locația telefonului.", - "import_step_1": "1. Autentificați-vă în contul vostru Google și descarcați Istoricul Locațiilor", - "import_step_2": "2. Deschideți această secțiune dupa descarcare. Datele se vor importa automat.", - "import_title": "Importa Istoric", "latest_news": "Ultimele Știri", "launch_done_header": "Configurare finalizata", "launch_done_subheader": "Telefonul a fost configurat cu succes. Nu uitați că puteți oricând sa modificați preferințele.", @@ -74,10 +78,6 @@ "location_enabled_title": "Jurnalul Locațiilor Activat", "logging_active": "Jurnal Pornit", "logging_inactive": "Jurnal Oprit", - "maps_import_button_text": "Importa istoric locații", - "maps_import_disclaimer": "Safe Paths nu are nici o afiliere cu Google și nu dezvaluie NICIODATĂ datele dumneavoastră.", - "maps_import_text": "Pentru a verifica dacă v-ați intersectat cu cineva găsit pozitiv COVID-19 înainte de a fi descarcat această aplicație, puteți importa istoricul personal al locațiilor dumneavoastră.", - "maps_import_title": "Hărți Google", "news_subtitle": "Cititi ultimele actualizări despre COVID-19 de la autoritațile de sănătate și alte surse.", "news_title": "Ultimele știri", "no_data": "Lipsa date", @@ -85,11 +85,15 @@ "push_at_risk_title": "Factor de risc identificat", "see_exposure_history": "Vezi istoricul expunerilor", "settings_title": "Tablou de bord", - "share_location_data": "Trimite locațiile", "team": "Echipa", "team_para": "Echipa noastră este formata dintr-un consorțiu de epidemiologi, ingineri, oameni de stiință de date, promotori a siguranței datelor personale, cat si profesori și cercetatori de la instituții cu reputație, ce includ: MIT, Harvard, Clinica Mayo, TripleBlind, EyeNetra, Ernst & Young și Link Ventures.", - "terms_of_use": "Termeni de folosire", - "tested_positive_subtitle": "Locațiile vizitate pot fi trimise autoritaților medicale, pot fi salvate ca rezervă, sau pot fi împărțite cu terți.", - "tested_positive_title": "Exportă istoricul locațiilor" + "terms_of_use": "Termeni de folosire" + }, + "share": { + "button_text": "Trimite locațiile", + "paragraph_first": "Dacă sunteți diagnosticat pozitiv cu COVID-19, vă rugăm să împărțiți istoricul traseelor pentru a ajuta la identificarea persoanelor ce au fost posibil infectate de dumneavoastră,", + "paragraph_second": "Traseele sunt exportate ca o listă simplă ce conține latitudine, longitudine și timpi, fără nici o altă informație de natură personală.", + "subtitle": "Locațiile vizitate pot fi trimise autoritaților medicale, pot fi salvate ca rezervă, sau pot fi împărțite cu terți.", + "title": "Exportă istoricul locațiilor" } } diff --git a/app/locales/ru.json b/app/locales/ru.json index e864c6037b..505ac4088b 100644 --- a/app/locales/ru.json +++ b/app/locales/ru.json @@ -1,99 +1,113 @@ { + "history": { + "no_exposure": "Неизвестно", + "possible_exposure": "Возможно", + "possible_exposure_para": "Есть вероятность, что вы контактировали или находились рядом с человеком, инфицированным COVID-19", + "what_does_this_mean": "Что это значит?", + "what_does_this_mean_para": "Судя по вашей GPS истории, есть вероятность, что вы контактировали или находились рядом с человеком, инфицированным COVID-19. Это не значит, что вы заразились, но такая опасность существует.\n\nПодробнее о том, как быть в этой ситуации, можно узнать на странице Клиники Мэйо.", + "what_if_no_symptoms": "Что, если у меня нет симптомов?", + "what_if_no_symptoms_para": "Если у вас нет симптомов, но вы хотите провериться, вы можете сделать это на сайте близлежащей организации здравоохранения.\n\nЛюди без симптомов заболевания иногда могут быть переносчиками болезни и заражать других. Важно учитывать собственный риск и опасность для окружающих, соблюдая социальную дистанцию и избегая контактов с большими группами людей или людьми из группы риска (пожилыми или людьми со слабым здоровьем)." + }, + "import": { + "button_text": "Импортировать последние местонахождения", + "google": { + "disclaimer": "Safe Paths не является партнером Google и не распространяет ваши данные.", + "title": "Google Карты", + "visit_button_text": "Перейти на Google Takeout" + }, + "subtitle": "Чтобы узнать, не контактировали ли вы с больным COVID-19 до установки приложения, импортируйте историю своих перемещений.", + "title": "Импортировать местонахождения" + }, "label": { - "about_title":"О нас", - "authorities_add_button_label":"Добавить надёжный источник", - "authorities_add_url":"Добавить организацию по ссылке", - "authorities_desc":"Выберите надёжные органы здравоохранения в вашем регионе, чтобы получать данные о зонах риска. Выберите название организации из международного реестра или введите ссылку организации, которая использует Safe Paths.", - "authorities_input_placeholder":"Вставьте ссылку сюда", - "authorities_no_sources":"Нет источника данных", - "authorities_removal_alert_cancel":"Отмена", - "authorities_removal_alert_desc":"Вы уверены, что хотите удалить этот источник данных?", - "authorities_removal_alert_proceed":"Продолжить", - "authorities_removal_alert_title":"Удалить организацию", - "authorities_title":"Надёжные источники", - "choose_provider_subtitle":"Чтобы получать информацию о возможных рисках, вам нужно подписаться на орган здравоохранения.", - "choose_provider_title":"Выбрать орган здравоохранения", - "commitment":"Гарантии", - "commitment_para":"Safe Paths ведет безопасную запись и отслеживание вашего взаимодействия с другими людьми, используя вашу геопозицию. При этом данные о вашем местонахождении НИКОГДА не передаются с вашего устройства без вашего согласия.", - "default_news_site_name":"Новости Safe Paths", - "event_history_subtitle":"Оцените собственные риски на основе информации от органов здравоохранения.", - "event_history_title":"История возможных рисков", - "export_para_1":"Если ваш тест на COVID-19 оказался положительным, пожалуйста, поделитесь историей своих перемещений с местными ответственными организациями.", - "export_para_2":"Местоположение передается в виде простого списка координат и времени, без дополнительной информации.", - "home_at_risk_header":"Возможно, вы в опасной зоне", - "home_at_risk_subsubtext":"Это не значит, что вы заразились.", - "home_at_risk_subtext":"Судя по вашей GPS истории, есть вероятность, что вы контактировали или находились рядом с человеком, инфицированным COVID-19.", - "home_enable_location":"Включить данные геопозиции", - "home_mayo_link_heading":"Подробнее о COVID-19", - "home_mayo_link_label":"от Kлиники Мэйо", - "home_next_steps":"Подробнее", - "home_no_contact_header":"Контактов не обнаружено", - "home_no_contact_subtext":"Согласно имеющимся данным, вы не находились рядом с человеком, больным COVID-19.", - "home_unknown_header":"Неизвестно", - "home_unknown_subtext":"Мы не сможем сказать, подвергались ли вы риску, пока вы не разрешите приложению доступ к вашей геопозиции", - "import_step_1":"1. Войти с помощью Google и скачать историю ваших перемещений", - "import_step_2":"2. После окончания загрузки откройте это окно заново. Данные импортируются автоматически.", - "import_title":"Импортировать местонахождения", - "latest_news":"Последние новости", - "launch_done_header":"Готово", - "launch_done_subheader":"Можно начинать. Помните, что вы в любой момент можете изменить настройки.", - "launch_enable_location":"Включить отслеживание местоположения", - "launch_enable_notif":"Включить уведомления", - "launch_finish_set_up":"Завершить настройку", - "launch_get_started":"Начать", - "launch_location_access":"Доступ к местоположению", - "launch_location_header":"Чтобы помнить, где вы были, телефону необходимо сохранять ваше местоположение.", - "launch_location_subheader":"Будьте уверены, данные хранятся только на вашем телефоне, если вы явно не решите поделиться.", - "launch_next":"Далее", - "launch_notif_header":"Вы получите уведомление, если вы окажетесь вблизи инфицированного человека.", - "launch_notif_subheader":"Мы побеспокоим вас только в случае риска попадания в опасную зону.", - "launch_notification_access":"Разрешить уведомления", - "launch_screen1_header":"Возвращение к привычной жизни начинается здесь.", - "launch_screen2_header":"Получайте уведомления, если вы были рядом с человеком, у которого впоследствии был диагностирован COVID-19.", - "launch_screen2_subheader":"Знание – сила.", - "launch_screen3_header":"Если ваш тест на COVID-19 оказался положительным, вы можете анонимно предоставить свои данные", - "launch_screen3_subheader":"Это способствует безопасности всего вашего окружения.", - "launch_screen4_header":"Всё под вашим контролем. Данные сохраняются только на вашем телефоне.", - "launch_screen4_subheader":"Если ваш тест на COVID-19 оказался положительным, только вы решаете, делиться ли данной информацией.", - "launch_set_up_phone":"Настроить телефон", - "legal_page_title":"Правовая информация", - "less_than_one_minute":"менее 1 минуты", - "loading_public_data":"идёт загрузка данных…", - "location_disabled_message":"Для работы COVID Safe Paths требуются службы геолокации.", - "location_disabled_title":"Отслеживание местонахождения было отключено", - "location_enabled_message":"COVID Safe Paths ведёт безопасную запись ваших GPS координат на данном устройстве раз в пять минут.", - "location_enabled_title":"COVID Safe Paths включено", - "maps_import_button_text":"Импортировать последние местонахождения", - "maps_import_disclaimer":"Safe Paths не является партнером Google и не распространяет ваши данные.", - "maps_import_text":"Чтобы узнать, не контактировали ли вы с больным COVID-19 до установки приложения, импортируйте историю своих перемещений. ", - "maps_import_title":"Google Карты", - "nCoV2019_url_info":"Подробнее о наборе данных этой карты", - "news_subtitle":"Будьте в курсе последних данных о COVID от вашей организации здравоохранения и не только.", - "news_title":"Последние новости", - "no_data":"Нет данных", - "notification_2_weeks_ago":"2 недели назад", - "notification_data_not_available":"Нет данных о возможных рисках заражения", - "notification_random_data_button":"Выбрать орган здравоохранения", - "notification_title":"Профиль рисков за 2 недели", - "notification_today":"сегодня", - "notification_warning_text":"Если в вашем регионе есть органы здравоохранения, вы можете подписаться на них, чтобы получать новости о рисках заражения.", - "notifications_exposure_format":"{{daysAgo}} дней назад вы находились рядом с инфицированным человеком в течение {{exposureTime}} минут.", - "notifications_exposure_format_today":"Сегодня вы находились рядом с инфицированным человеком в течение {{exposureTime}} минут.", - "notifications_exposure_format_yesterday":"Вчера вы находились рядом с инфицированным человеком в течение {{exposureTime}} минут.", - "notifications_no_exposure":"Нет выявленных мест возможного заражения COVID-19 за последние две недели.", - "overlap_found_button_label":"Публичные данные загружены", - "overlap_no_results_button_label":"Публичные данные загружены", - "overlap_para_1":"Зелёным отмечена история ваших перемещений\\n\\nБледно-фиолетовые круги обозначают публичный набор данных", - "overlap_title":"Смотреть зоны пересечения", - "push_at_risk_message":"Ваш путь пересекался с путем человека, больным COVID-19", - "push_at_risk_title":"Возможно, вы в зоне риска", - "settings_title":"Контрольная панель", - "share_location_data":"Поделится данными о местоположении", - "show_overlap":"Нажмите, чтобы увидеть публичный набор данных", - "team":"Команда", - "team_para":"Наша команда состоит из эпидемиологов, инженеров, специалистов по обработке данных, евангелистов цифровой безопасности, профессоров и исследователей институтов с мировым именем, таких как МТИ (MIT), Гарвард, Клиника Мэйо, TripleBlind, EyeNetra, Ernst & Young и Link Ventures.", - "terms_of_use":"Условия использования", - "tested_positive_subtitle":"Ваши личные данные могут быть предоставлены органам здравоохранения, сохранены в резервную копию или переданы иным образом.", - "tested_positive_title":"Поделиться историей перемещений" + "about_title": "О нас", + "authorities_add_button_label": "Добавить надёжный источник", + "authorities_add_url": "Добавить организацию по ссылке", + "authorities_desc": "Выберите надёжные органы здравоохранения в вашем регионе, чтобы получать данные о зонах риска. Выберите название организации из международного реестра или введите ссылку организации, которая использует Safe Paths.", + "authorities_input_placeholder": "Вставьте ссылку сюда", + "authorities_no_sources": "Нет источника данных", + "authorities_removal_alert_cancel": "Отмена", + "authorities_removal_alert_desc": "Вы уверены, что хотите удалить этот источник данных?", + "authorities_removal_alert_proceed": "Продолжить", + "authorities_removal_alert_title": "Удалить организацию", + "authorities_title": "Надёжные источники", + "choose_provider_subtitle": "Чтобы получать информацию о возможных рисках, вам нужно подписаться на орган здравоохранения.", + "choose_provider_title": "Выбрать орган здравоохранения", + "commitment": "Гарантии", + "commitment_para": "Safe Paths ведет безопасную запись и отслеживание вашего взаимодействия с другими людьми, используя вашу геопозицию. При этом данные о вашем местонахождении НИКОГДА не передаются с вашего устройства без вашего согласия.", + "default_news_site_name": "Новости Safe Paths", + "event_history_subtitle": "Оцените собственные риски на основе информации от органов здравоохранения.", + "event_history_title": "История возможных рисков", + "home_at_risk_header": "Возможно, вы в опасной зоне", + "home_at_risk_subsubtext": "Это не значит, что вы заразились.", + "home_at_risk_subtext": "Судя по вашей GPS истории, есть вероятность, что вы контактировали или находились рядом с человеком, инфицированным COVID-19.", + "home_enable_location": "Включить данные геопозиции", + "home_mayo_link_heading": "Подробнее о COVID-19", + "home_mayo_link_label": "от Kлиники Мэйо", + "home_no_contact_header": "Контактов не обнаружено", + "home_no_contact_subtext": "Согласно имеющимся данным, вы не находились рядом с человеком, больным COVID-19.", + "home_setting_off_header": "Неизвестно", + "home_setting_off_subtext": "Мы не сможем сказать, подвергаетесь ли вы риску, пока вы не включите историю перемещений на экране настроек.", + "home_unknown_header": "Неизвестно", + "home_unknown_subtext": "Мы не сможем сказать, подвергались ли вы риску, пока вы не разрешите приложению доступ к вашей геопозиции", + "latest_news": "Последние новости", + "launch_done_header": "Готово", + "launch_done_subheader": "Можно начинать. Помните, что вы в любой момент можете изменить настройки.", + "launch_enable_location": "Включить отслеживание местоположения", + "launch_enable_notif": "Включить уведомления", + "launch_finish_set_up": "Завершить настройку", + "launch_get_started": "Начать", + "launch_location_access": "Доступ к местоположению", + "launch_location_header": "Чтобы помнить, где вы были, телефону необходимо сохранять ваше местоположение.", + "launch_location_subheader": "Будьте уверены, данные хранятся только на вашем телефоне, если вы явно не решите поделиться.", + "launch_next": "Далее", + "launch_notif_header": "Вы получите уведомление, если вы окажетесь вблизи инфицированного человека.", + "launch_notif_subheader": "Мы побеспокоим вас только в случае риска попадания в опасную зону.", + "launch_notification_access": "Разрешить уведомления", + "launch_screen1_header": "Возвращение к привычной жизни начинается здесь.", + "launch_screen2_header": "Получайте уведомления, если вы были рядом с человеком, у которого впоследствии был диагностирован COVID-19.", + "launch_screen2_subheader": "Знание – сила.", + "launch_screen3_header": "Если ваш тест на COVID-19 оказался положительным, вы можете анонимно предоставить свои данные", + "launch_screen3_subheader": "Это способствует безопасности всего вашего окружения.", + "launch_screen4_header": "Всё под вашим контролем. Данные сохраняются только на вашем телефоне.", + "launch_screen4_subheader": "Если ваш тест на COVID-19 оказался положительным, только вы решаете, делиться ли данной информацией.", + "launch_set_up_phone": "Настроить телефон", + "legal_page_title": "Правовая информация", + "loading_public_data": "идёт загрузка данных…", + "location_disabled_message": "Для работы COVID Safe Paths требуются службы геолокации.", + "location_disabled_title": "Отслеживание местонахождения было отключено", + "location_enabled_message": "COVID Safe Paths ведёт безопасную запись ваших GPS координат на данном устройстве раз в пять минут.", + "location_enabled_title": "COVID Safe Paths включено", + "logging_active": "Отслеживание местонахождения включено", + "logging_inactive": "Отслеживание местонахождения отключено", + "news_subtitle": "Будьте в курсе последних данных о COVID от вашей организации здравоохранения и не только.", + "news_title": "Последние новости", + "no_data": "Нет данных", + "push_at_risk_message": "Ваш путь пересекался с путем человека, больным COVID-19", + "push_at_risk_title": "Возможно, вы в зоне риска", + "see_exposure_history": "Смотреть историю возможных рисков", + "settings_title": "Контрольная панель", + "team": "Команда", + "team_para": "Наша команда состоит из эпидемиологов, инженеров, специалистов по обработке данных, евангелистов цифровой безопасности, профессоров и исследователей институтов с мировым именем, таких как МТИ (MIT), Гарвард, Клиника Мэйо, TripleBlind, EyeNetra, Ernst & Young и Link Ventures.", + "terms_of_use": "Условия использования" + }, + "onboarding": { + "eula_checkbox": "Я принимаю лицензионное соглашение", + "eula_continue": "Продолжить", + "eula_message": "Примите, чтобы пользоваться Safe Paths" + }, + "share": { + "button_text": "Поделится данными о местоположении", + "paragraph_first": "Если ваш тест на COVID-19 оказался положительным, пожалуйста, поделитесь историей своих перемещений с местными ответственными организациями.", + "paragraph_second": "Местоположение передается в виде простого списка координат и времени, без дополнительной информации.", + "subtitle": "Ваши личные данные могут быть предоставлены органам здравоохранения, сохранены в резервную копию или переданы иным образом.", + "title": "Поделиться историей перемещений" + }, + "version_update": { + "alert_label": "COVID Safe Paths is outdated", + "alert_sublabel": "Обновить версию приложения?", + "later": "Позднее", + "push_notification_message": "Пожалуйста, обновите приложение", + "push_notification_title": "Вы используете устаревшую версию приложения", + "update": "Обновить" } } diff --git a/app/locales/sk.json b/app/locales/sk.json index 1fd20fe5b1..1c774dedfe 100644 --- a/app/locales/sk.json +++ b/app/locales/sk.json @@ -1,109 +1,93 @@ { + "history": { + "no_exposure": "Žiadne vystavenie sa riziku", + "possible_exposure": "Možné vystavenie sa riziku", + "possible_exposure_para": "Je možné, že si bol/a v kontakte s niekým alebo bol/a v blízkosti niekoho, kto bol pozitívne testovaný na COVID-19", + "what_does_this_mean": "Čo to znamená", + "what_does_this_mean_para": "Na základe histórie tvojho GPS je možné, že si bol/a v kontakte alebo bol/a v blízkosti osoby, komu bol diagnostikovaný COVID-19. To neznamená, že si infikovaný/á ale môžeš byť. \n\nĎalšie informácie o tom ako postupovať ďalej nájdeš na webovej stránke Mayo Clinic.", + "what_if_no_symptoms": "Čo ak nevykazujem žiadny symptómy?", + "what_if_no_symptoms_para": "Ak nemáš žiadne symptómy, ale aj tak by si sa chcel/a otestovať, navštív najbližšiu testovaciu stanicu. \n\nJednotlivci, ktorí nevykazujú žiadne symptómy, môžu prenášať infekciu a nakaziť iných ľudí. Buď opatrný/á pri spoločenskom dištancovaní a prichádzaní do kontaktu s veľkými skupinami ľudí alebo ohrozenými jednotlivcami (staršími osobami a osobami s vážnymi zdravotnými problémami), je dôležité, aby si zvládol/la svoje riziko aj riziko pre ostatných." + }, + "import": { + "button_text": "Importovať históriu polôh", + "google": { + "disclaimer": "Safe Paths nemá žiadne prepojenie s Google a nikdy nezdieľa tvoje údaje.", + "title": "Google Maps" + }, + "subtitle": "Ak chceš zistiť, či si sa stretol/la s niekým, kto má COVID-19 pred stiahnutím tejto aplikácie, môžeš importovať tvoju osobnú históriu geografických polôh.", + "title": "Importovať polohy" + }, "label": { - "about_title":"O", - "authorities_add_button_label":"Pridať dôveryhodný zdroj", - "authorities_add_url":"Pridať úrad prostredníctvom URL", - "authorities_desc":"Ak chceš získať údaje o tvojom vystavení sa riziku, vyber dôveryhodné zdravotné úrady v tvojej oblasti. Vyber meno z globálneho registra alebo zadaj webovú adresu poskytnutú orgánom, ktorú implementoval Safe Paths. ", - "authorities_input_placeholder":"Sem vlož tvoje URL", - "authorities_no_sources":"Zatiaľ žiadny zdroj údajov", - "authorities_removal_alert_cancel":"Ukončiť", - "authorities_removal_alert_desc":"Si si istý/á, že chceš odstrániť zdroj údajov tohto úradu?", - "authorities_removal_alert_proceed":"Pokračovať", - "authorities_removal_alert_title":"Odstrániť úrad", - "authorities_title":"Dôveryhodné zdroje", - "choose_provider_subtitle":"Na to, aby si bol/a informovaný/á o tvojom vystavení sa riziku, je potrebné prihlásiť sa na odber od zdravotného úradu.", - "choose_provider_title":"Vybrať zdravotný úrad", - "commitment":"Záväzok", - "commitment_para":"Safe Paths bezpečne zaznamenáva a kontroluje tvoj prienik s ľuďmi, ktorí používajú tvoju polohu. Tvoje dáta NIKDY neopustia tvoj telefón bez tvojho súhlasu.", - "default_news_site_name":"Správy o Safe Paths", - "event_history_subtitle":"Zisti o svojom osobnom vystavení sa riziku na základe informácií zdieľaných zdravotnými úradmi.", - "event_history_title":"História vystavenia riziku", - "export_para_1":"Ak si testovaný/á pozitívne na COVID-19, prosím, prispej zdieľaním svojej polohy so všetkými miestnymi orgánmi.", - "export_para_2":"Poloha je zdieľaná ako jednoduchý zoznam časov a miest, bez akýchkoľvek ďalších informácií.", - "home_at_risk_header":"Môžeš byť vystavený/á riziku", - "home_at_risk_subsubtext":"Toto znamená, že nie si nakazený/á", - "home_at_risk_subtext":"Na základe histórie tvojho GPS, je možné, že si bol/a v kontakte alebo blízko niekoho, komu bol diagnostikovaný COVID-19", - "home_enable_location":"Povoliť údaje o polohe", - "home_mayo_link_heading":"Viac informácií o COVID-19", - "home_mayo_link_label":"z Mayo Clinic", - "home_no_contact_header":"Žiadny známy kontakt", - "home_no_contact_subtext":"Na základe dostupných dát si nebol/a v blízkosti nikoho, kto je nahlásený ako pozitívne testovaný na COVID-19.", - "home_unknown_header":"Neznámy", - "home_unknown_subtext":"Kým nepovolíš aplikácii prístup k tvojej polohe, nemôžeme ti povedať, či si v ohrození. ", - "import_step_1":"1. Prihlás sa do Google účtu a stiahni svoju históriu polôh", - "import_step_2":"2. Po stiahnutí klikni znovu na túto obrazovku. Dáta budú importované automaticky.", - "import_title":"Importovať polohy", - "latest_news":"Najnovšie oznámenia", - "launch_done_header":"Všetko je ukončené", - "launch_done_subheader":"Môžeš začať. Nezabudni, že svoje preferencie môžeš kedykoľvek aktualizovať.", - "launch_enable_location":"Umožniť lokalizáciu", - "launch_enable_notif":"Umožniť upozornenia", - "launch_finish_set_up":"Dokončiť nastavenia", - "launch_get_started":"Začať", - "launch_location_access":"Prístup k polohe", - "launch_location_header":"Tvoj telefón potrebuje ukladanie tvojej polohy na to, aby si zaznamenával, kde sa pohybuješ. ", - "launch_location_subheader":"Neboj sa, informácie nikdy neopustia tvoj telefón, pokiaľ sa ich vyslovene nerozhodneš zdieľať.", - "launch_next":"Ďalej", - "launch_notif_header":"Upozornenia ti oznámia, či si sa stretol/la s infikovanou osobou.", - "launch_notif_subheader":"Nebudeme ťa obťažovať, len čo sa týka zdieľania aktualizácie o tvojich možných vystaveniach sa riziku.", - "launch_notification_access":"Povoliť oznámenia", - "launch_screen1_header":"Tu začína cesta späť k normálu.", - "launch_screen2_header":"Dostať upozornenie, ak si sa stretol/la s niekým, komu bol neskôr diagnostikovaný COVID-19.", - "launch_screen2_subheader":"Vedomosti sú sila.", - "launch_screen3_header":"Ak si bol/a pozitívne testovaný, môžeš sa rozhodnúť, či budeš zdieľať svoje dáta anonymne", - "launch_screen3_subheader":"Čo pomáha chrániť celú tvoju komunitu.", - "launch_screen4_header":"Máš všetko pod úplnou kontrolou. Údaje sú uložené iba na tvojom telefóne.", - "launch_screen4_subheader":"Ak si testovaný/á pozitívne, môžeš sa sám/a rozhodnúť, či budeš zdieľať svoje dáta.", - "launch_set_up_phone":"Nastaviť môj telefón", - "legal_page_title":"Legálny", - "less_than_one_minute":"Menej ako jedna minúta", - "loading_public_data":"Dáta sa načítavajú", - "location_disabled_message":"COVID Safe Paths vyžaduje lokalizačné služby.", - "location_disabled_title":"Sledovanie polohy bolo deaktivované.", - "location_enabled_message":"COVID Safe Paths bezpečne ukladá vaše GPS súradnice každých päť minút.", - "location_enabled_title":"COVID Safe Paths bol povolený", - "maps_import_button_text":"Importovať históriu polôh", - "maps_import_disclaimer":"Safe Paths nemá žiadne prepojenie s Google a nikdy nezdieľa tvoje údaje.", - "maps_import_text":"Ak chceš zistiť, či si sa stretol/la s niekým, kto má COVID-19 pred stiahnutím tejto aplikácie, môžeš importovať tvoju osobnú históriu geografických polôh.", - "maps_import_title":"Google Maps", - "nCoV2019_url_info":"Viac informácií o súbore údajov pre túto mapu", - "news_subtitle":"Prečítaj si informácie o najnovších aktualizáciách týkajúcich sa COVID-19 od vášho zdravotného úradu a vo všeobecnosti.", - "news_title":"Najnovšie oznámenia", - "no_data":"Žiadne údaje", - "notification_2_weeks_ago":"Pred 2 týždňami", - "notification_data_not_available":"Nie sú k dispozícii žiadne údaje o vystavení sa riziku.", - "notification_select_authority":"Vyber si zdravotný úrad", - "notification_title":"Profil vystavenia sa riziku za posledné dva týždne", - "notification_today":"dnes", - "notification_warning_text":"Ak zdravotný úrad existuje v tvojej oblasti, môžeš sa prihlásiť na odber pravidelných aktualizácií o vystavení sa riziku.", - "notifications_exposure_format":"Pred {{daysAgo}} dňami si sa stretol/la s niekým infekčným počas {{exposureTime}} minút.", - "notifications_exposure_format_today":"Dnes si sa stretol/la s niekým infekčným počas {{exposureTime}} minút.", - "notifications_exposure_format_yesterday":"Včera si sa stretol/la s niekým infekčným počas {{exposureTime}} minút.", - "notifications_no_exposure":"Žiadne známe vystavenia sa COVID-19 počas posledných dvoch týždňov.", - "overlap_found_button_label":"Verejné dáta načítané", - "overlap_no_results_button_label":"Verejné dáta načítané", - "overlap_para_1":"Zelené označenie predstavuje históriu tvojich geografických polôh\\n\\nFialové kruhy predstavujú súbor verejných údajov", - "overlap_title":"Prezri prekrytie", - "push_at_risk_message":"Stretol/la si sa s pacientom COVID-19", - "push_at_risk_title":"Môžeš byť v ohrození", - "see_exposure_history":"Pozri si svoju históriu vystavenia sa riziku", - "settings_title":"Panel", - "share_location_data":"Zdieľaj svoje údaje o polohe ", - "show_overlap":"Kliknutím sa zobrazí verejný súbor údajov", - "team":"Tím", - "team_para":"Náš tím je zložený z konzorcia epidemiológov, inžinierov, vedcov, evanjelistov v oblasti digitálneho súkromia, profesorov a výskumníkov z uznávaných inštitúcií zahŕňajúc: MIT, Hardward, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young a Link Ventures.", - "terms_of_use":"Podmienky používania", - "tested_positive_subtitle":"Tvoje osobné údaje môžu byť odovzdané zdravotným úradom, zálohované alebo inak zdieľané.", - "tested_positive_title":"Zdieľaj históriu tvojej lokalizácie" + "about_title": "O", + "authorities_add_button_label": "Pridať dôveryhodný zdroj", + "authorities_add_url": "Pridať úrad prostredníctvom URL", + "authorities_desc": "Ak chceš získať údaje o tvojom vystavení sa riziku, vyber dôveryhodné zdravotné úrady v tvojej oblasti. Vyber meno z globálneho registra alebo zadaj webovú adresu poskytnutú orgánom, ktorú implementoval Safe Paths.", + "authorities_input_placeholder": "Sem vlož tvoje URL", + "authorities_no_sources": "Zatiaľ žiadny zdroj údajov", + "authorities_removal_alert_cancel": "Ukončiť", + "authorities_removal_alert_desc": "Si si istý/á, že chceš odstrániť zdroj údajov tohto úradu?", + "authorities_removal_alert_proceed": "Pokračovať", + "authorities_removal_alert_title": "Odstrániť úrad", + "authorities_title": "Dôveryhodné zdroje", + "choose_provider_subtitle": "Na to, aby si bol/a informovaný/á o tvojom vystavení sa riziku, je potrebné prihlásiť sa na odber od zdravotného úradu.", + "choose_provider_title": "Vybrať zdravotný úrad", + "commitment": "Záväzok", + "commitment_para": "Safe Paths bezpečne zaznamenáva a kontroluje tvoj prienik s ľuďmi, ktorí používajú tvoju polohu. Tvoje dáta NIKDY neopustia tvoj telefón bez tvojho súhlasu.", + "default_news_site_name": "Správy o Safe Paths", + "event_history_subtitle": "Zisti o svojom osobnom vystavení sa riziku na základe informácií zdieľaných zdravotnými úradmi.", + "event_history_title": "História vystavenia riziku", + "home_at_risk_header": "Môžeš byť vystavený/á riziku", + "home_at_risk_subsubtext": "Toto znamená, že nie si nakazený/á", + "home_at_risk_subtext": "Na základe histórie tvojho GPS, je možné, že si bol/a v kontakte alebo blízko niekoho, komu bol diagnostikovaný COVID-19", + "home_enable_location": "Povoliť údaje o polohe", + "home_mayo_link_heading": "Viac informácií o COVID-19", + "home_mayo_link_label": "z Mayo Clinic", + "home_no_contact_header": "Žiadny známy kontakt", + "home_no_contact_subtext": "Na základe dostupných dát si nebol/a v blízkosti nikoho, kto je nahlásený ako pozitívne testovaný na COVID-19.", + "home_unknown_header": "Neznámy", + "home_unknown_subtext": "Kým nepovolíš aplikácii prístup k tvojej polohe, nemôžeme ti povedať, či si v ohrození.", + "latest_news": "Najnovšie oznámenia", + "launch_done_header": "Všetko je ukončené", + "launch_done_subheader": "Môžeš začať. Nezabudni, že svoje preferencie môžeš kedykoľvek aktualizovať.", + "launch_enable_location": "Umožniť lokalizáciu", + "launch_enable_notif": "Umožniť upozornenia", + "launch_finish_set_up": "Dokončiť nastavenia", + "launch_get_started": "Začať", + "launch_location_access": "Prístup k polohe", + "launch_location_header": "Tvoj telefón potrebuje ukladanie tvojej polohy na to, aby si zaznamenával, kde sa pohybuješ.", + "launch_location_subheader": "Neboj sa, informácie nikdy neopustia tvoj telefón, pokiaľ sa ich vyslovene nerozhodneš zdieľať.", + "launch_next": "Ďalej", + "launch_notif_header": "Upozornenia ti oznámia, či si sa stretol/la s infikovanou osobou.", + "launch_notif_subheader": "Nebudeme ťa obťažovať, len čo sa týka zdieľania aktualizácie o tvojich možných vystaveniach sa riziku.", + "launch_notification_access": "Povoliť oznámenia", + "launch_screen1_header": "Tu začína cesta späť k normálu.", + "launch_screen2_header": "Dostať upozornenie, ak si sa stretol/la s niekým, komu bol neskôr diagnostikovaný COVID-19.", + "launch_screen2_subheader": "Vedomosti sú sila.", + "launch_screen3_header": "Ak si bol/a pozitívne testovaný, môžeš sa rozhodnúť, či budeš zdieľať svoje dáta anonymne", + "launch_screen3_subheader": "Čo pomáha chrániť celú tvoju komunitu.", + "launch_screen4_header": "Máš všetko pod úplnou kontrolou. Údaje sú uložené iba na tvojom telefóne.", + "launch_screen4_subheader": "Ak si testovaný/á pozitívne, môžeš sa sám/a rozhodnúť, či budeš zdieľať svoje dáta.", + "launch_set_up_phone": "Nastaviť môj telefón", + "legal_page_title": "Legálny", + "loading_public_data": "Dáta sa načítavajú", + "location_disabled_message": "COVID Safe Paths vyžaduje lokalizačné služby.", + "location_disabled_title": "Sledovanie polohy bolo deaktivované.", + "location_enabled_message": "COVID Safe Paths bezpečne ukladá vaše GPS súradnice každých päť minút.", + "location_enabled_title": "COVID Safe Paths bol povolený", + "news_subtitle": "Prečítaj si informácie o najnovších aktualizáciách týkajúcich sa COVID-19 od vášho zdravotného úradu a vo všeobecnosti.", + "news_title": "Najnovšie oznámenia", + "no_data": "Žiadne údaje", + "push_at_risk_message": "Stretol/la si sa s pacientom COVID-19", + "push_at_risk_title": "Môžeš byť v ohrození", + "see_exposure_history": "Pozri si svoju históriu vystavenia sa riziku", + "settings_title": "Panel", + "team": "Tím", + "team_para": "Náš tím je zložený z konzorcia epidemiológov, inžinierov, vedcov, evanjelistov v oblasti digitálneho súkromia, profesorov a výskumníkov z uznávaných inštitúcií zahŕňajúc: MIT, Hardward, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young a Link Ventures.", + "terms_of_use": "Podmienky používania" }, - "history": { - "no_exposure":"Žiadne vystavenie sa riziku", - "possible_exposure_para":"Je možné, že si bol/a v kontakte s niekým alebo bol/a v blízkosti niekoho, kto bol pozitívne testovaný na COVID-19", - "possible_exposure":"Možné vystavenie sa riziku", - "timeline":"Časový prehľad", - "what_does_this_mean_para":"Na základe histórie tvojho GPS je možné, že si bol/a v kontakte alebo bol/a v blízkosti osoby, komu bol diagnostikovaný COVID-19. To neznamená, že si infikovaný/á ale môžeš byť. \\n\\nĎalšie informácie o tom ako postupovať ďalej nájdeš na webovej stránke Mayo Clinic.", - "what_does_this_mean":"Čo to znamená", - "what_if_no_symptoms_para":"Ak nemáš žiadne symptómy, ale aj tak by si sa chcel/a otestovať, navštív najbližšiu testovaciu stanicu. \\n\\nJednotlivci, ktorí nevykazujú žiadne symptómy, môžu prenášať infekciu a nakaziť iných ľudí. Buď opatrný/á pri spoločenskom dištancovaní a prichádzaní do kontaktu s veľkými skupinami ľudí alebo ohrozenými jednotlivcami (staršími osobami a osobami s vážnymi zdravotnými problémami), je dôležité, aby si zvládol/la svoje riziko aj riziko pre ostatných.", - "what_if_no_symptoms":"Čo ak nevykazujem žiadny symptómy?" + "share": { + "button_text": "Zdieľaj svoje údaje o polohe", + "subtitle": "Tvoje osobné údaje môžu byť odovzdané zdravotným úradom, zálohované alebo inak zdieľané.", + "title": "Zdieľaj históriu tvojej lokalizácie" } } diff --git a/app/locales/vi.json b/app/locales/vi.json index 4cf9228d35..640e8b5952 100644 --- a/app/locales/vi.json +++ b/app/locales/vi.json @@ -1,88 +1,85 @@ { + "import": { + "button_text": "Nhập lịch sử di chuyển trước đây", + "google": { + "disclaimer": "Safe Paths không liên kết với Google và không bao giờ chia sẻ dữ liệu của bạn.", + "title": "Bản đồ Google" + }, + "subtitle": "Để biết bạn có tiếp xúc với người bị nhiễm COVID-19 trước khi bạn bắt đầu sử dụng Ứng dụng này hay không, bạn có thể nhập lịch sử vị trí trước đó của mình.", + "title": "Nhập các vị trí địa điểm" + }, "label": { "about_title": "Giới thiệu", "authorities_add_button_label": "Thêm nguồn thông tin tin cậy", - "authorities_add_url":"Thêm cơ quan có thẩm quyền qua liên kết", - "authorities_desc":"Chọn cơ sở Y tế phụ trách khu vực của bạn để tải về dữ liệu phơi nhiễm. Chọn tên từ danh sách được công bố hoặc nhập địa chỉ web được cung cấp bởi các cơ quan có thẩm quyền đã triển khai Safe Paths", - "authorities_input_placeholder":"Dán đường dẫn liên kết tại đây", - "authorities_no_sources":"Chưa có nguồn dữ liệu", - "authorities_removal_alert_cancel":"Hủy", - "authorities_removal_alert_desc":"Bạn có chắc chắn muốn xóa nguôn thông tin của cơ quan có thẩm quyền này không?", - "authorities_removal_alert_proceed":"Tiếp tục", - "authorities_removal_alert_title":"Xoá Cơ quan có thẩm quyền", - "authorities_title":"Nguồn thông tin tin cậy", - "choose_provider_subtitle":"Để được thông báo về khả năng lây nhiễm, bạn cần đăng ký liên kết với dữ liệu từ cơ sở Y tế hoặc cơ quan có thẩm quyền.", - "choose_provider_title":"Chọn cơ sở y tế", - "commitment":"Cam kết", - "commitment_para":"Safe Paths lưu lại và kiểm tra sự tiếp xúc của bạn với người khác một cách bảo mật và an toàn sử dụng thông tin vị trí của bạn. Dữ liệu vị trí của bạn KHÔNG BAO GIỜ được gửi ra ngoài mà không có sự cho phép và giám sát của bạn", - "default_news_site_name":"Thông tin cập nhật về Safe Paths", - "event_history_subtitle":"Biết được khả năng phơi nhiễm của bạn dựa vào dữ liệu mà các cơ sở y tế chia sẻ.", - "event_history_title":"Lịch sử phơi nhiễm", - "export_para_1":"Nếu kết quả kiểm tra của bạn dương tính với COVID-19, bạn nên chia sẻ lịch sử vị trí của mình với cơ quan có thẩm quyền tại địa phương", - "export_para_2":"Vị trí được chia sẻ dưới dạng danh sách đơn giản gồm thời gian và toạ độ, không bao gồm các thông tin cá nhân khác", - "home_at_risk_header":"Bạn có khả năng bị phơi nhiễm", - "home_at_risk_subsubtext":"Điều này không có nghĩa là bạn đã bị nhiễm bệnh. ", - "home_at_risk_subtext":"Dựa vào lịch sử GPS của bạn, có thể bạn đã tiếp xúc gần với người được chẩn đoán mắc COVID-19.", - "home_enable_location":"Kích hoạt dữ liệu định vị ", - "home_mayo_link_heading":"Xem thêm thông tin về COVID-19", - "home_mayo_link_label":"Từ Mayo Clinic", - "home_no_contact_header":"Không có tiếp xúc gần được ghi lại", - "home_no_contact_subtext":"Theo nguồn dữ liệu có sẵn, bạn chưa có tiếp xúc gần với người được chẩn đoán mắc COVID-19.", - "home_unknown_header":"Không xác định", - "home_unknown_subtext":"Chúng tôi không thể gửi cảnh báo nguy cơ phơi nhiễm cho bạn nếu bạn không cho phép ứng dụng truy cập vị trí.", - "import_step_1":"1. Đăng nhập vào tài khoản Google và tải xuống Lịch sử vị trí. (Location history)", - "import_step_2":"2. Sau khi tải xuống, mở lại màn hình này. Dữ liệu sẽ tự động cập nhập.", - "import_title":"Nhập các vị trí địa điểm", - "latest_news":"Tin mới nhất", - "launch_done_header":"Đã hoàn thành", - "launch_done_subheader":"Cài đặt của bạn đã hoàn tất. Bạn luôn có thể cài đặt lại cấu hình bất cứ lúc nào", - "launch_enable_location":"Cho phép truy cập vị trí", - "launch_enable_notif":"Cho phép gửi thông báo", - "launch_finish_set_up":"Hoàn thành cài đặt", - "launch_get_started":"Bắt đầu", - "launch_location_access":"Truy cập vị trí", - "launch_location_header":"Để biết được những nơi bạn đã đến, điện thoại cần được cho phép lưu lại vị trí của bạn", - "launch_location_subheader":"Đừng lo lắng, dữ liệu không bao giờ được gửi ra ngoài khỏi thiết bị của bạn trừ khi bạn quyết định chia sẻ.", - "launch_next":"Tiếp theo", - "launch_notif_header":"Thông báo sẽ cho biết khi bạn có khả năng tiếp xúc gần với người bị xác định dương tính", - "launch_notif_subheader":"Chúng tôi sẽ không làm phiền bạn ngoại trừ việc chia sẻ thông tin về các rủi ro phơi nhiễm của bạn", - "launch_notification_access":"Cho phép bật thông báo", - "launch_screen1_header":"Khởi đầu của việc đưa cuộc sống trở lại bình thường", - "launch_screen2_header":"Nhận thông báo nếu bạn có khả năng tiêp xúc gần với người được chẩn đoán nhiễm COVID-19.", - "launch_screen2_subheader":"Thông tin, kiến thức là sức mạnh", - "launch_screen3_header":"Nếu kết quả xét nghiệm của bạn dương tính, bạn có thể chọn ẨN DANH để chia sẻ dữ liệu vị trí di chuyển của mình, không ai biết đó là dữ liệu của bạn. ", - "launch_screen3_subheader":"Điều này sẽ giúp bảo vệ cộng đồng", - "launch_screen4_header":"Bạn hoàn toàn có quyền kiểm soát dữ liệu. Nó chỉ được lưu trên điện thoại của bạn.", - "launch_screen4_subheader":"Nếu bạn kiểm tra dương tính, CHỈ MÌNH BẠN có quyền quyết định có chia sẻ dữ liệu di chuyển của mình hay không", - "launch_set_up_phone":"Cài đặt điện thoại", - "legal_page_title":"Pháp lý", - "less_than_one_minute":"còn lại dưới 1 phút", - "loading_public_data":"đang tải dữ liệu ...", - "location_disabled_message":"COVID Safe Paths yêu cầu dịch cho phép sử dụng dịch vụ vị trí", - "location_disabled_title":"Theo dõi vị trí đã bị vô hiệu hóa", - "location_enabled_message":"Safe Paths sẽ lưu thông tin GPS của bạn một cách an toàn, chỉ trên thiết bị của bạn, 5 phút một lần ", - "location_enabled_title":"Safe Paths đã được kích hoạt", - "maps_import_button_text":"Nhập lịch sử di chuyển trước đây", - "maps_import_disclaimer":"Safe Paths không liên kết với Google và không bao giờ chia sẻ dữ liệu của bạn.", - "maps_import_text":"Để biết bạn có tiếp xúc với người bị nhiễm COVID-19 trước khi bạn bắt đầu sử dụng Ứng dụng này hay không, bạn có thể nhập lịch sử vị trí trước đó của mình.", - "maps_import_title":"Bản đồ Google", - "nCoV2019_url_info":"Để biết thêm thông tin về các dữ liệu cho bản đồ này", - "news_subtitle":"Xem các thông tin mới nhất về COVID-19 từ ", - "news_title":"Tin mới nhất", - "no_data":"Không có dữ liệu", - "overlap_found_button_label":"Đã tải xong dữ liệu", - "overlap_no_results_button_label":"Đã tải xong dữ liệu ", - "overlap_para_1":"Đường màu xanh thể hiện lịch sử vị trí của bạn\\n\\nVòng tròn màu tím nhạt thể hiện dữ liệu được chia sẻ bởi cơ quan có thẩm quyền", - "overlap_title":"Kiểm tra khả năng phơi nhiễm", - "push_at_risk_message":"Bạn có thể có tiếp xúcgaanf với bệnh nhân COVID-19", - "push_at_risk_title":"Bạn có thể gặp rủi ro", - "settings_title":"Bảng dữ liệu chung", - "share_location_data":"Chia sẻ dữ liệu vị trí", - "show_overlap":"Nhấn vào đây để xem dữ liệu công cộng", - "team":"Nhóm phát triển", - "team_para":"Chúng tôi gồm một nhóm các nhà dịch tễ học, kỹ sư, nhà khoa học dữ liệu, nhà truyền bá quyền riêng tư kỹ thuật số, giáo sư và nhà nghiên cứu từ các tổ chức có uy tín, bao gồm: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young và Link Ventures.", - "terms_of_use":"Điều khoản sử dụng", - "tested_positive_subtitle":"Dữ liệu riêng tư của bạn có thể được chuyển đến các cơ quan y tế, sao lưu hoặc chia sẻ.", - "tested_positive_title":"Chia sẻ lịch sử vị trí" + "authorities_add_url": "Thêm cơ quan có thẩm quyền qua liên kết", + "authorities_desc": "Chọn cơ sở Y tế phụ trách khu vực của bạn để tải về dữ liệu phơi nhiễm. Chọn tên từ danh sách được công bố hoặc nhập địa chỉ web được cung cấp bởi các cơ quan có thẩm quyền đã triển khai Safe Paths", + "authorities_input_placeholder": "Dán đường dẫn liên kết tại đây", + "authorities_no_sources": "Chưa có nguồn dữ liệu", + "authorities_removal_alert_cancel": "Hủy", + "authorities_removal_alert_desc": "Bạn có chắc chắn muốn xóa nguôn thông tin của cơ quan có thẩm quyền này không?", + "authorities_removal_alert_proceed": "Tiếp tục", + "authorities_removal_alert_title": "Xoá Cơ quan có thẩm quyền", + "authorities_title": "Nguồn thông tin tin cậy", + "choose_provider_subtitle": "Để được thông báo về khả năng lây nhiễm, bạn cần đăng ký liên kết với dữ liệu từ cơ sở Y tế hoặc cơ quan có thẩm quyền.", + "choose_provider_title": "Chọn cơ sở y tế", + "commitment": "Cam kết", + "commitment_para": "Safe Paths lưu lại và kiểm tra sự tiếp xúc của bạn với người khác một cách bảo mật và an toàn sử dụng thông tin vị trí của bạn. Dữ liệu vị trí của bạn KHÔNG BAO GIỜ được gửi ra ngoài mà không có sự cho phép và giám sát của bạn", + "default_news_site_name": "Thông tin cập nhật về Safe Paths", + "event_history_subtitle": "Biết được khả năng phơi nhiễm của bạn dựa vào dữ liệu mà các cơ sở y tế chia sẻ.", + "event_history_title": "Lịch sử phơi nhiễm", + "home_at_risk_header": "Bạn có khả năng bị phơi nhiễm", + "home_at_risk_subsubtext": "Điều này không có nghĩa là bạn đã bị nhiễm bệnh.", + "home_at_risk_subtext": "Dựa vào lịch sử GPS của bạn, có thể bạn đã tiếp xúc gần với người được chẩn đoán mắc COVID-19.", + "home_enable_location": "Kích hoạt dữ liệu định vị", + "home_mayo_link_heading": "Xem thêm thông tin về COVID-19", + "home_mayo_link_label": "Từ Mayo Clinic", + "home_no_contact_header": "Không có tiếp xúc gần được ghi lại", + "home_no_contact_subtext": "Theo nguồn dữ liệu có sẵn, bạn chưa có tiếp xúc gần với người được chẩn đoán mắc COVID-19.", + "home_unknown_header": "Không xác định", + "home_unknown_subtext": "Chúng tôi không thể gửi cảnh báo nguy cơ phơi nhiễm cho bạn nếu bạn không cho phép ứng dụng truy cập vị trí.", + "latest_news": "Tin mới nhất", + "launch_done_header": "Đã hoàn thành", + "launch_done_subheader": "Cài đặt của bạn đã hoàn tất. Bạn luôn có thể cài đặt lại cấu hình bất cứ lúc nào", + "launch_enable_location": "Cho phép truy cập vị trí", + "launch_enable_notif": "Cho phép gửi thông báo", + "launch_finish_set_up": "Hoàn thành cài đặt", + "launch_get_started": "Bắt đầu", + "launch_location_access": "Truy cập vị trí", + "launch_location_header": "Để biết được những nơi bạn đã đến, điện thoại cần được cho phép lưu lại vị trí của bạn", + "launch_location_subheader": "Đừng lo lắng, dữ liệu không bao giờ được gửi ra ngoài khỏi thiết bị của bạn trừ khi bạn quyết định chia sẻ.", + "launch_next": "Tiếp theo", + "launch_notif_header": "Thông báo sẽ cho biết khi bạn có khả năng tiếp xúc gần với người bị xác định dương tính", + "launch_notif_subheader": "Chúng tôi sẽ không làm phiền bạn ngoại trừ việc chia sẻ thông tin về các rủi ro phơi nhiễm của bạn", + "launch_notification_access": "Cho phép bật thông báo", + "launch_screen1_header": "Khởi đầu của việc đưa cuộc sống trở lại bình thường", + "launch_screen2_header": "Nhận thông báo nếu bạn có khả năng tiêp xúc gần với người được chẩn đoán nhiễm COVID-19.", + "launch_screen2_subheader": "Thông tin, kiến thức là sức mạnh", + "launch_screen3_header": "Nếu kết quả xét nghiệm của bạn dương tính, bạn có thể chọn ẨN DANH để chia sẻ dữ liệu vị trí di chuyển của mình, không ai biết đó là dữ liệu của bạn.", + "launch_screen3_subheader": "Điều này sẽ giúp bảo vệ cộng đồng", + "launch_screen4_header": "Bạn hoàn toàn có quyền kiểm soát dữ liệu. Nó chỉ được lưu trên điện thoại của bạn.", + "launch_screen4_subheader": "Nếu bạn kiểm tra dương tính, CHỈ MÌNH BẠN có quyền quyết định có chia sẻ dữ liệu di chuyển của mình hay không", + "launch_set_up_phone": "Cài đặt điện thoại", + "legal_page_title": "Pháp lý", + "loading_public_data": "đang tải dữ liệu ...", + "location_disabled_message": "COVID Safe Paths yêu cầu dịch cho phép sử dụng dịch vụ vị trí", + "location_disabled_title": "Theo dõi vị trí đã bị vô hiệu hóa", + "location_enabled_message": "Safe Paths sẽ lưu thông tin GPS của bạn một cách an toàn, chỉ trên thiết bị của bạn, 5 phút một lần", + "location_enabled_title": "Safe Paths đã được kích hoạt", + "news_subtitle": "Xem các thông tin mới nhất về COVID-19 từ", + "news_title": "Tin mới nhất", + "no_data": "Không có dữ liệu", + "push_at_risk_message": "Bạn có thể có tiếp xúcgaanf với bệnh nhân COVID-19", + "push_at_risk_title": "Bạn có thể gặp rủi ro", + "settings_title": "Bảng dữ liệu chung", + "team": "Nhóm phát triển", + "team_para": "Chúng tôi gồm một nhóm các nhà dịch tễ học, kỹ sư, nhà khoa học dữ liệu, nhà truyền bá quyền riêng tư kỹ thuật số, giáo sư và nhà nghiên cứu từ các tổ chức có uy tín, bao gồm: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young và Link Ventures.", + "terms_of_use": "Điều khoản sử dụng" + }, + "share": { + "button_text": "Chia sẻ dữ liệu vị trí", + "paragraph_first": "Nếu kết quả kiểm tra của bạn dương tính với COVID-19, bạn nên chia sẻ lịch sử vị trí của mình với cơ quan có thẩm quyền tại địa phương", + "paragraph_second": "Vị trí được chia sẻ dưới dạng danh sách đơn giản gồm thời gian và toạ độ, không bao gồm các thông tin cá nhân khác", + "subtitle": "Dữ liệu riêng tư của bạn có thể được chuyển đến các cơ quan y tế, sao lưu hoặc chia sẻ.", + "title": "Chia sẻ lịch sử vị trí" } } diff --git a/app/locales/zh-Hant.json b/app/locales/zh_Hant.json similarity index 64% rename from app/locales/zh-Hant.json rename to app/locales/zh_Hant.json index f0ebee90fb..7aef2d98a2 100644 --- a/app/locales/zh-Hant.json +++ b/app/locales/zh_Hant.json @@ -1,4 +1,13 @@ { + "import": { + "button_text": "過去の位置情報をインポートする", + "google": { + "disclaimer": "Safe Paths與Google不存在從屬關係也不會分享您的資料", + "title": "Google 地圖" + }, + "subtitle": "確認您下載此應用程式前,是否與新冠病毒確診者接觸,您可以載入您個人位置歷史資訊", + "title": "輸入位置資訊" + }, "label": { "about_title": "關於", "authorities_add_button_label": "新增可信來源", @@ -18,20 +27,14 @@ "default_news_site_name": "SafePaths 新聞", "event_history_subtitle": "從疾病管制機構共享的數據了解您個人接觸風險", "event_history_title": "接觸歷史", - "export_para_1": "如果您新冠病毒檢測結果為陽性,請您盡己所能分享您的位置歷史資訊給在地的疾病管制機構", - "export_para_2": "位置資訊僅分享您所在位置與時間,並無其他多餘資訊", "home_at_risk_header": "您可能有高接觸風險", "home_at_risk_subsubtext": "這並不代表您被感染了", "home_at_risk_subtext": "根據您的路徑歷史資訊,您有可能曾經與新冠病毒確診者或接觸者接觸", "home_enable_location": "開啟位置數據", - "home_next_steps": "知道更多", "home_no_contact_header": "無已知接觸", "home_no_contact_subtext": "根據已知數據,您並未接觸任何新冠病毒確診者", "home_unknown_header": "未知", - "home_unknown_subtext": "我們無法告知您是否曝露在接觸風險中,除非您允許此應用程式取得您位置資訊", - "import_step_1": "1. 登陸您Google帳號,並下載您的位置資訊", - "import_step_2": "2. 下載後,請再度打開此頁面,資料將自動導入", - "import_title": "輸入位置資訊", + "home_unknown_subtext": "我們無法告知您是否曝露在接觸風險中,除非您允許此應用程式取得您位置資訊\"", "latest_news": "最新新聞", "launch_done_header": "全部完成", "launch_done_subheader": "您已經準備完成。請記得,您可以隨時更新您的偏好", @@ -41,12 +44,12 @@ "launch_get_started": "開始", "launch_location_access": "取得位置", "launch_location_header": "為比對您與確診者的路徑,此應用程式需要取得您手機的位置資訊", - "launch_location_subheader": "別擔心,除非您確認要分享資訊,此資訊不會離開您手機", + "launch_location_subheader": "別擔心,除非您確認要分享資訊,此資訊不會離開您手機\"", "launch_next": "下一個", "launch_notif_header": "如果您曾經與確診者路徑重疊,我們會推送通知讓您知曉", "launch_notif_subheader": "除非通知您潛在的接觸風險,我們不會打擾您", "launch_notification_access": "允許通知", - "launch_screen1_header": "安全之路,由此開始", + "launch_screen1_header": "安全之路,由此開始。", "launch_screen2_header": "立即得知您是否與新冠肺炎確診者重疊路徑", "launch_screen2_subheader": "知識就是力量", "launch_screen3_header": "若您確診了,您可以選擇匿名的分享您的資料", @@ -55,43 +58,25 @@ "launch_screen4_subheader": "如果您確診了,您可以自主選擇是否分享資料", "launch_set_up_phone": "設定我的手機", "legal_page_title": "法遵", - "less_than_one_minute": "少於一分鐘", "loading_public_data": "資料下載中...", "location_disabled_message": "COVID Safe Paths 需要位置服務", "location_disabled_title": "位置資訊已禁用", "location_enabled_message": "COVID Safe Paths 每五分鐘在此裝置安全的儲存您的路徑資訊", "location_enabled_title": "啟用 COVID Safe Paths", - "maps_import_button_text": "載入歷史路徑", - "maps_import_disclaimer": "Safe Paths與Google不存在從屬關係也不會分享您的資料", - "maps_import_text": "確認您下載此應用程式前,是否與新冠病毒確診者接觸,您可以載入您個人位置歷史資訊", - "maps_import_title": "Google 地圖", - "nCoV2019_url_info": "更多關於此地圖資料來源的資訊", "news_subtitle": "從您在地與其他主要健康權威機構中,閱讀更多關於新冠病毒的最新資訊", "news_title": "最新資訊", "no_data": "沒有資料", - "notification_2_weeks_ago": "兩週以前", - "notification_data_not_available": "無可用的病毒接觸數據", - "notification_random_data_button": "選擇疾病管制機構", - "notification_title": "兩週內的接觸風險報告", - "notification_today": "今天", - "notification_warning_text": "若您所處地區的疾病管制機構存在,您可以訂閱並取得定期的接觸風險報告", - "notifications_exposure_format": "{{daysAgo}}天之前,您曾經與確診者重疊路徑{{exposureTime}}分鐘", - "notifications_exposure_format_today": "今天你與確診者重疊路徑{{exposureTime}}分鐘", - "notifications_exposure_format_yesterday": "昨天您與確診者重疊路徑{{exposureTime}}分鐘.", - "notifications_no_exposure": "過去兩星期內,沒有與新冠肺炎確診者接觸", - "overlap_found_button_label": "已下載公開數據", - "overlap_no_results_button_label": "已下載公開數據", - "overlap_para_1": "綠色路徑代表您的歷史路徑\n\n淡紫色圓環代表公開數據集", - "overlap_title": "確認路徑重疊", "push_at_risk_message": "您與新冠肺炎確診者路徑重疊", "push_at_risk_title": "您有可能曝露在接觸風險中", "settings_title": "看板", - "share_location_data": "分享位置資訊", - "show_overlap": "點擊閱覽公開數據集", "team": "團隊", "team_para": "我們團隊是由一群來自知名機構的傳染病學家、工程師、資料科學家、數位隱私倡導者、教授以及研究員、包含:MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young and Link Ventures.", - "terms_of_use": "使用條款", - "tested_positive_subtitle": "您的隱私資料可以被分享給疾病管制機構、背份、或者分享", - "tested_positive_title": "分享位置歷史資訊" + "terms_of_use": "使用條款" + }, + "share": { + "button_text": "分享位置資訊", + "paragraph_second": "如果您新冠病毒檢測結果為陽性,請您盡己所能分享您的位置歷史資訊給在地的疾病管制機構", + "subtitle": "您的隱私資料可以被分享給疾病管制機構、背份、或者分享", + "title": "分享位置歷史資訊" } } diff --git a/ios/en.lproj/InfoPlist.strings b/ios/en.lproj/InfoPlist.strings index dcdcb7578a..7cab9e2c8c 100644 --- a/ios/en.lproj/InfoPlist.strings +++ b/ios/en.lproj/InfoPlist.strings @@ -1,9 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. All rights reserved."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; - "NSLocationAlwaysUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; - "NSLocationWhenInUseUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; - "NSMotionUsageDescription" = "We use your accelerometer for accurate location history."; - -"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. All rights reserved."; diff --git a/ios/en.lproj/Localizable.strings b/ios/en.lproj/Localizable.strings index 7617492c5c..dfc62e51bc 100644 --- a/ios/en.lproj/Localizable.strings +++ b/ios/en.lproj/Localizable.strings @@ -1,5 +1,4 @@ -/* Title of notification when app is closed on iOS */ -"ios.app_closed_alert_title" = "COVID Safe Paths Was Closed"; - /* Body text of notification when app is closed on iOS */ "ios.app_closed_alert_text" = "COVID Safe Paths requires to be running."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths Was Closed"; diff --git a/ios/es.lproj/InfoPlist.strings b/ios/es.lproj/InfoPlist.strings index 8b13789179..d35f9e2fe0 100644 --- a/ios/es.lproj/InfoPlist.strings +++ b/ios/es.lproj/InfoPlist.strings @@ -1 +1,5 @@ - +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Todos los derechos reservados."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Tu historial de ubicaciones se guardará en su dispositivo y solo se compartirá con si elige hacerlo."; +"NSLocationAlwaysUsageDescription" = "Tu historial de ubicaciones se guardará en su dispositivo y solo se compartirá con si elige hacerlo."; +"NSLocationWhenInUseUsageDescription" = "Tu historial de ubicaciones se guardará en su dispositivo y solo se compartirá con si elige hacerlo."; +"NSMotionUsageDescription" = "Utilizamos su acelerómetro precisar tu historial de ubicación."; diff --git a/ios/es.lproj/Localizable.strings b/ios/es.lproj/Localizable.strings index e69de29bb2..0a0ec76df7 100644 --- a/ios/es.lproj/Localizable.strings +++ b/ios/es.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths requiere estar en ejecución."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths se ha cerrado"; diff --git a/ios/fr.lproj/Localizable.strings b/ios/fr.lproj/Localizable.strings index e69de29bb2..9f17aeef8e 100644 --- a/ios/fr.lproj/Localizable.strings +++ b/ios/fr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths a besoin des services de position."; diff --git a/ios/ht.lproj/InfoPlist.strings b/ios/ht.lproj/InfoPlist.strings new file mode 100644 index 0000000000..9dcdeb321e --- /dev/null +++ b/ios/ht.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Tout dwa rezève."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Lis lokalizasyon ou pral anrejistre sou aparèy ou a, e yo ap pataje selman si ou chwazi pou ou fè sa."; +"NSLocationAlwaysUsageDescription" = "Lis lokalizasyon ou pral anrejistre sou aparèy ou a, e yo ap pataje selman si ou chwazi pou ou fè sa."; +"NSLocationWhenInUseUsageDescription" = "Lis lokalizasyon ou pral anrejistre sou aparèy ou a, e yo ap pataje selman si ou chwazi pou ou fè sa."; +"NSMotionUsageDescription" = "Nou itilize akseleromèt ou a pou lis lokaliszasyon an egzat"; diff --git a/ios/ht.lproj/Localizable.strings b/ios/ht.lproj/Localizable.strings new file mode 100644 index 0000000000..8474eef28c --- /dev/null +++ b/ios/ht.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "Chase Kowona (\"Safe Paths\") mande poul fonksyone"; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "Chase Kowona (\"Safe Paths\") te fèmen"; diff --git a/ios/it.lproj/InfoPlist.strings b/ios/it.lproj/InfoPlist.strings index e69de29bb2..3ee77ff7c8 100644 --- a/ios/it.lproj/InfoPlist.strings +++ b/ios/it.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Tutti i diritti riservati."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "La cronologia delle tue posizioni sarà salvata sul dispositivo e condivisa con gli altri solo se vorrai."; +"NSLocationAlwaysUsageDescription" = "La cronologia delle tue posizioni sarà salvata sul dispositivo e condivisa con gli altri solo se vorrai."; +"NSLocationWhenInUseUsageDescription" = "La cronologia delle tue posizioni sarà salvata sul dispositivo e condivisa con gli altri solo se vorrai."; +"NSMotionUsageDescription" = "Utilizziamo l'accelerometro per un'accurata cronologia delle posizioni."; diff --git a/ios/it.lproj/Localizable.strings b/ios/it.lproj/Localizable.strings index e69de29bb2..7c1690577d 100644 --- a/ios/it.lproj/Localizable.strings +++ b/ios/it.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths deve essere attivo."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths è stata chiusa."; diff --git a/ios/ml.lproj/InfoPlist.strings b/ios/ml.lproj/InfoPlist.strings index e69de29bb2..7b0efadbff 100644 --- a/ios/ml.lproj/InfoPlist.strings +++ b/ios/ml.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "പകർപ്പവകാശം © 2020 പാത്ത് ചെക്ക് ഇങ്ക്. എല്ലാ അവകാശങ്ങളും നിക്ഷിപ്തം."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം നിങ്ങളുടെ ഉപകരണത്തിൽ സംരക്ഷിക്കപ്പെടും, നിങ്ങൾ തീരുമാനിക്കുകയാണെങ്കിൽ മാത്രമേ മറ്റുള്ളവരുമായി പങ്കിടൂ."; +"NSLocationAlwaysUsageDescription" = "നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം നിങ്ങളുടെ ഉപകരണത്തിൽ സംരക്ഷിക്കപ്പെടും, നിങ്ങൾ തീരുമാനിക്കുകയാണെങ്കിൽ മാത്രമേ മറ്റുള്ളവരുമായി പങ്കിടൂ."; +"NSLocationWhenInUseUsageDescription" = "നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം നിങ്ങളുടെ ഉപകരണത്തിൽ സംരക്ഷിക്കപ്പെടും, നിങ്ങൾ തീരുമാനിക്കുകയാണെങ്കിൽ മാത്രമേ മറ്റുള്ളവരുമായി പങ്കിടുക."; +"NSMotionUsageDescription" = "കൃത്യമായ ലൊക്കേഷൻ ചരിത്രത്തിനായി ഞങ്ങൾ നിങ്ങളുടെ ആക്‌സിലറോമീറ്റർ ഉപയോഗിക്കുന്നു."; diff --git a/ios/ml.lproj/Localizable.strings b/ios/ml.lproj/Localizable.strings index e69de29bb2..2a2329a648 100644 --- a/ios/ml.lproj/Localizable.strings +++ b/ios/ml.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID സെയ്ഫ് പാത്ത് പ്രവർത്തിപ്പിക്കേണ്ടതുണ്ട്."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID സെയ്ഫ് പാത്ത് പ്രവർത്തനം നിർത്തി"; diff --git a/ios/nl.lproj/InfoPlist.strings b/ios/nl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..09f35654fe --- /dev/null +++ b/ios/nl.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Alle rechten voorbehouden."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Uw locatiegeschiedenis wordt op uw apparaat opgeslagen en alleen met anderen gedeeld als u daarvoor kiest."; +"NSLocationAlwaysUsageDescription" = "Uw locatiegeschiedenis wordt op uw apparaat opgeslagen en alleen met anderen gedeeld als u daarvoor kiest."; +"NSLocationWhenInUseUsageDescription" = "Uw locatiegeschiedenis wordt op uw apparaat opgeslagen en alleen met anderen gedeeld als u daarvoor kiest."; +"NSMotionUsageDescription" = "We gebruiken uw versnellingsmeter om een nauwkeurige locatiegeschiedenis samen te stellen."; diff --git a/ios/nl.lproj/Localizable.strings b/ios/nl.lproj/Localizable.strings new file mode 100644 index 0000000000..b73749767b --- /dev/null +++ b/ios/nl.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths moet actief zijn"; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths was afgesloten"; diff --git a/ios/ru.lproj/InfoPlist.strings b/ios/ru.lproj/InfoPlist.strings index e69de29bb2..db48cead4a 100644 --- a/ios/ru.lproj/InfoPlist.strings +++ b/ios/ru.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Авторское право © 2020 Path Check Inc. Все права защищены."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "История ваших перемещений будет сохраняться только на вашем устройстве. Она будет доступна другим пользователям, только если вы решите ей поделиться."; +"NSLocationAlwaysUsageDescription" = "История ваших перемещений будет сохраняться только на вашем устройстве. Она будет доступна другим пользователям, только если вы решите ей поделиться."; +"NSLocationWhenInUseUsageDescription" = "История ваших перемещений будет сохраняться только на вашем устройстве. Она будет доступна другим пользователям, только если вы решите ей поделиться."; +"NSMotionUsageDescription" = "Мы используем акселерометр вашего устройства для уточнения истории ваших перемещений."; diff --git a/ios/ru.lproj/Localizable.strings b/ios/ru.lproj/Localizable.strings index e69de29bb2..e5d7e390f3 100644 --- a/ios/ru.lproj/Localizable.strings +++ b/ios/ru.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths должно быть запущено."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths было закрыто"; diff --git a/ios/zz-ZZ.lproj/InfoPlist.strings b/ios/zz-ZZ.lproj/InfoPlist.strings new file mode 100644 index 0000000000..7cab9e2c8c --- /dev/null +++ b/ios/zz-ZZ.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. All rights reserved."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; +"NSLocationAlwaysUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; +"NSLocationWhenInUseUsageDescription" = "Your location history will be saved on your device, and only shared with others if you choose to do so."; +"NSMotionUsageDescription" = "We use your accelerometer for accurate location history."; diff --git a/ios/zz-ZZ.lproj/Localizable.strings b/ios/zz-ZZ.lproj/Localizable.strings new file mode 100644 index 0000000000..dfc62e51bc --- /dev/null +++ b/ios/zz-ZZ.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths requires to be running."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "COVID Safe Paths Was Closed"; diff --git a/package.json b/package.json index bb720194e3..4f05e3d47c 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,9 @@ "test:e2e:iphone11": "detox test -c iphone11.sim --loglevel=warn", "test:e2e:iphone-se": "detox test -c iphone-se.sim --loglevel=warn", "test:e2e:iphone8": "detox test -c iphone8.sim --loglevel=warn", - "i18n:extract": "i18next", "i18n:check": "./app/locales/check.sh", + "i18n:extract": "i18next", + "i18n:pull": "./app/locales/pull.sh", "test": "jest --config=./jest/config.js", "update-snapshots": "jest --config=./jest/config.js --updateSnapshot", "test:dev_setup": "bats __tests__/dev_setup.test.bats" From aa101e37fc37fe4c8ae7425ccb76494adf16e9d6 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 18:21:42 -0700 Subject: [PATCH 38/60] Add ja and fil languages (#700) --- app/locales/ar.json | 1 + app/locales/en.json | 1 + app/locales/es.json | 1 + app/locales/fil.json | 1 + app/locales/fr.json | 1 + app/locales/ht.json | 1 + app/locales/id.json | 1 + app/locales/it.json | 1 + app/locales/ja.json | 1 + app/locales/languages.js | 51 ++++++++++++++++++++++------------------ app/locales/ml.json | 1 + app/locales/nl.json | 1 + app/locales/pl.json | 1 + app/locales/pt_BR.json | 1 + app/locales/ro.json | 1 + app/locales/ru.json | 1 + app/locales/sk.json | 1 + app/locales/vi.json | 1 + app/locales/zh_Hant.json | 1 + 19 files changed, 46 insertions(+), 23 deletions(-) diff --git a/app/locales/ar.json b/app/locales/ar.json index fcc0f34f05..d8b133686c 100644 --- a/app/locales/ar.json +++ b/app/locales/ar.json @@ -1,4 +1,5 @@ { + "_display_name": "العربية", "label": { "about_title": "نبذة عنا", "authorities_add_button_label": "إضافة هيئة صحية موثوقة", diff --git a/app/locales/en.json b/app/locales/en.json index 59dd54ea80..a098560158 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -1,4 +1,5 @@ { + "_display_name": "English", "about": { "dimensions": "Dimensions:", "operating_system_abbr": "OS:", diff --git a/app/locales/es.json b/app/locales/es.json index 155be0dd6c..3600551b77 100644 --- a/app/locales/es.json +++ b/app/locales/es.json @@ -1,4 +1,5 @@ { + "_display_name": "Español", "history": { "no_exposure": "No ha habido contacto", "possible_exposure": "Posible", diff --git a/app/locales/fil.json b/app/locales/fil.json index 1dfe61f2b7..c66f1f939c 100644 --- a/app/locales/fil.json +++ b/app/locales/fil.json @@ -1,4 +1,5 @@ { + "_display_name": "Filipino", "import": { "title": "Mag-import ng lokasyon" }, diff --git a/app/locales/fr.json b/app/locales/fr.json index 8a8a9910d4..a55d4457a7 100644 --- a/app/locales/fr.json +++ b/app/locales/fr.json @@ -1,4 +1,5 @@ { + "_display_name": "Français", "import": { "button_text": "Importer les positions passées", "google": { diff --git a/app/locales/ht.json b/app/locales/ht.json index 9a769a7353..05f3aa8f82 100644 --- a/app/locales/ht.json +++ b/app/locales/ht.json @@ -1,4 +1,5 @@ { + "_display_name": "Kreyòl ayisyen", "history": { "no_exposure": "Pa gen kontak", "possible_exposure": "Posib", diff --git a/app/locales/id.json b/app/locales/id.json index 02eb130b3a..cbcacfa433 100644 --- a/app/locales/id.json +++ b/app/locales/id.json @@ -1,4 +1,5 @@ { + "_display_name": "Indonesia", "history": { "no_exposure": "Tidak ada paparan", "possible_exposure": "Kemungkinan Terpapar", diff --git a/app/locales/it.json b/app/locales/it.json index 6b40a894d0..533e6bdfda 100644 --- a/app/locales/it.json +++ b/app/locales/it.json @@ -1,4 +1,5 @@ { + "_display_name": "Italiano", "history": { "no_exposure": "Non noto", "possible_exposure": "Possibile", diff --git a/app/locales/ja.json b/app/locales/ja.json index 0d70d7f294..7145e01a70 100644 --- a/app/locales/ja.json +++ b/app/locales/ja.json @@ -1,4 +1,5 @@ { + "_display_name": "日本語", "import": { "button_text": "過去の位置情報をインポートする", "google": { diff --git a/app/locales/languages.js b/app/locales/languages.js index 48706e4fd7..d98a1b6805 100644 --- a/app/locales/languages.js +++ b/app/locales/languages.js @@ -11,10 +11,12 @@ import { GetStoreData, SetStoreData } from '../helpers/General'; import ar from './ar.json'; import en from './en.json'; import es from './es.json'; +import fil from './fil.json'; import fr from './fr.json'; import ht from './ht.json'; import id from './id.json'; import it from './it.json'; +import ja from './ja.json'; import ml from './ml.json'; import nl from './nl.json'; import pl from './pl.json'; @@ -28,13 +30,9 @@ import zh_Hant from './zh_Hant.json'; // Refer this for checking the codes and creating new folders https://developer.chrome.com/webstore/i18n // Adding/updating a language: -// 1. Update i18next-parser.config.js to ensure the xy language is in "locales" -// 2. run: npm run i18n:extract -// 3. All known/new keys will be added into xy.json -// - any removed keys will be put into xy_old.json, do not commit this file -// 4. Update translations as needed -// 5. REMOVE all empty translations. e.g. "key": "", this will allow fallback to the default: English -// 6. import xyIndex from `./xy.json` and add the language to the block at the bottom +// 1. Add the language in Lokalise +// 2. run: yarn i18n:pull with your lokalise token, see app/locales/pull.sh instructions +// 3. import xy from `./xy.json` and add the language to the language block /** Fetch the user language override, if any */ export async function getUserLocaleOverride() { @@ -70,23 +68,26 @@ export async function setUserLocaleOverride(locale) { await SetStoreData(LANG_OVERRIDE, locale); } +/* eslint-disable no-underscore-dangle */ /** Languages only available in dev builds. */ const DEV_LANGUAGES = __DEV__ ? { - ar: { label: 'العربية', translation: ar }, - es: { label: 'Español', translation: es }, - fr: { label: 'Français', translation: fr }, - id: { label: 'Indonesia', translation: id }, - it: { label: 'Italiano', translation: it }, - ml: { label: 'മലയാളം', translation: ml }, - nl: { label: 'Nederlands', translation: nl }, - pl: { label: 'Polski', translation: pl }, - pt_BR: { label: 'Portugues do Brasil', translation: pt_BR }, - ro: { label: 'Română', translation: ro }, - ru: { label: 'Русский', translation: ru }, - sk: { label: 'Slovak', translation: sk }, - vi: { label: 'Vietnamese', translation: vi }, - zh_Hant: { label: '繁體中文', translation: zh_Hant }, + ar: { label: ar._display_name, translation: ar }, + es: { label: es._display_name, translation: es }, + fil: { label: fil._display_name, translation: fil }, + fr: { label: fr._display_name, translation: fr }, + id: { label: id._display_name, translation: id }, + it: { label: it._display_name, translation: it }, + ja: { label: ja._display_name, translation: ja }, + ml: { label: ml._display_name, translation: ml }, + nl: { label: nl._display_name, translation: nl }, + pl: { label: pl._display_name, translation: pl }, + pt_BR: { label: pt_BR._display_name, translation: pt_BR }, + ro: { label: ro._display_name, translation: ro }, + ru: { label: ru._display_name, translation: ru }, + sk: { label: sk._display_name, translation: sk }, + vi: { label: vi._display_name, translation: vi }, + zh_Hant: { label: zh_Hant._display_name, translation: zh_Hant }, } : {}; @@ -99,11 +100,12 @@ i18next.use(initReactI18next).init({ fallbackLng: 'en', // If language detector fails returnEmptyString: false, resources: { - en: { label: 'English', translation: en }, - ht: { label: 'Kreyòl ayisyen', translation: ht }, + en: { label: en._display_name, translation: en }, + ht: { label: ht._display_name, translation: ht }, ...DEV_LANGUAGES, }, }); +/* eslint-enable no-underscore-dangle */ /** The known locale list */ export const LOCALE_LIST = Object.entries(i18next.options.resources) @@ -152,3 +154,6 @@ setLocale(supportedDeviceLanguageOrEnglish()); getUserLocaleOverride().then(locale => locale && setLocale(locale)); export default i18next; + +// do not remove, this will force the yarn i18n:extract to export this key +i18next.t('_display_name'); diff --git a/app/locales/ml.json b/app/locales/ml.json index 12fe87edd9..1db5fdd549 100644 --- a/app/locales/ml.json +++ b/app/locales/ml.json @@ -1,4 +1,5 @@ { + "_display_name": "മലയാളം", "history": { "no_exposure": "സാധ്യതയില്ല", "possible_exposure": "സാധ്യത", diff --git a/app/locales/nl.json b/app/locales/nl.json index 8b286f4bde..14d7564531 100644 --- a/app/locales/nl.json +++ b/app/locales/nl.json @@ -1,4 +1,5 @@ { + "_display_name": "Nederlands", "history": { "no_exposure": "Geen blootstelling", "possible_exposure": "Mogelijk", diff --git a/app/locales/pl.json b/app/locales/pl.json index 369c680aeb..7d66b48ec9 100644 --- a/app/locales/pl.json +++ b/app/locales/pl.json @@ -1,4 +1,5 @@ { + "_display_name": "Polski", "label": { "about_title": "O", "authorities_add_button_label": "Dodaj zaufane źródło", diff --git a/app/locales/pt_BR.json b/app/locales/pt_BR.json index 8218e5eb97..d16021298b 100644 --- a/app/locales/pt_BR.json +++ b/app/locales/pt_BR.json @@ -1,4 +1,5 @@ { + "_display_name": "Portugues do Brasil", "history": { "no_exposure": "Sem exposição conhecida", "possible_exposure": "Exposição possível", diff --git a/app/locales/ro.json b/app/locales/ro.json index 71afe2a43f..a620c3c1a9 100644 --- a/app/locales/ro.json +++ b/app/locales/ro.json @@ -1,4 +1,5 @@ { + "_display_name": "Română", "history": { "no_exposure": "Totul ok", "possible_exposure": "Expunere posibilă", diff --git a/app/locales/ru.json b/app/locales/ru.json index 505ac4088b..caa0b18126 100644 --- a/app/locales/ru.json +++ b/app/locales/ru.json @@ -1,4 +1,5 @@ { + "_display_name": "Русский", "history": { "no_exposure": "Неизвестно", "possible_exposure": "Возможно", diff --git a/app/locales/sk.json b/app/locales/sk.json index 1c774dedfe..6e59959096 100644 --- a/app/locales/sk.json +++ b/app/locales/sk.json @@ -1,4 +1,5 @@ { + "_display_name": "Slovak", "history": { "no_exposure": "Žiadne vystavenie sa riziku", "possible_exposure": "Možné vystavenie sa riziku", diff --git a/app/locales/vi.json b/app/locales/vi.json index 640e8b5952..513368d059 100644 --- a/app/locales/vi.json +++ b/app/locales/vi.json @@ -1,4 +1,5 @@ { + "_display_name": "Tiếng Việt", "import": { "button_text": "Nhập lịch sử di chuyển trước đây", "google": { diff --git a/app/locales/zh_Hant.json b/app/locales/zh_Hant.json index 7aef2d98a2..0d79398bdc 100644 --- a/app/locales/zh_Hant.json +++ b/app/locales/zh_Hant.json @@ -1,4 +1,5 @@ { + "_display_name": "繁體中文", "import": { "button_text": "過去の位置情報をインポートする", "google": { From 0811cc10912abf0355ea7c91a501d0bf89fe8ebc Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Mon, 27 Apr 2020 19:48:39 -0700 Subject: [PATCH 39/60] Fallback to English translations in e2e tests (#697) --- e2e/helpers/language.js | 13 +++++++++---- package.json | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/e2e/helpers/language.js b/e2e/helpers/language.js index f416997722..371cd335ac 100644 --- a/e2e/helpers/language.js +++ b/e2e/helpers/language.js @@ -1,3 +1,5 @@ +import merge from 'lodash/merge'; + import * as english from '../../app/locales/en.json'; import * as haitian from '../../app/locales/ht.json'; @@ -6,7 +8,10 @@ const languageStrings = { 'ht-HT': haitian, }; -export const languages = Object.keys(languageStrings).map(locale => [ - locale, - languageStrings[locale], -]); +export const languages = Object.entries(languageStrings).map( + ([locale, strings]) => [ + locale, + // fall back to english if not found in the locale strings + merge({}, english, strings), + ], +); diff --git a/package.json b/package.json index 4f05e3d47c..648b28cddd 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "emotion-theming": "^10.0.27", "i18next": "^19.3.3", "js-yaml": "^3.13.1", + "lodash": "^4.17.15", "patch-package": "^6.2.2", "pluralize": "^8.0.0", "postinstall-postinstall": "^2.0.0", From 0540716daa2827aedfade00f281d914f2dc4da01 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Tue, 28 Apr 2020 08:48:59 -0500 Subject: [PATCH 40/60] Fix e2e authority auto sub tests (#706) Signed-off-by: Patrick Erichsen --- .../NoAuthSubscriptionPermission.spec.js | 17 ++++++++++++++--- e2e/pages/EnableAuthoritySubscription.po.js | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/e2e/onboarding/NoAuthSubscriptionPermission.spec.js b/e2e/onboarding/NoAuthSubscriptionPermission.spec.js index 35e117c196..6ee32db090 100644 --- a/e2e/onboarding/NoAuthSubscriptionPermission.spec.js +++ b/e2e/onboarding/NoAuthSubscriptionPermission.spec.js @@ -1,6 +1,7 @@ /* eslint-disable jest/expect-expect */ import { languages } from '../helpers/language'; import { navigateThroughOnboarding } from '../helpers/onboarding'; +import Home from '../pages/Home.po.js'; describe.each(languages)( `Healthcare Authority auto subscription in %s`, @@ -16,9 +17,19 @@ describe.each(languages)( }); }); - it('Shows auto subscription as unset', async () => { - await navigateThroughOnboarding(languageStrings); - await device.takeScreenshot('No Auto Subscription'); + describe('When skipping the auto subscribe step', () => { + beforeAll(async () => { + await navigateThroughOnboarding(languageStrings); + }); + + it('Shows auto subscribe as unset', async () => { + await Home.takeScreenshot(); + }); + }); + + afterAll(async () => { + await device.uninstallApp(); + await device.installApp(); }); }, ); diff --git a/e2e/pages/EnableAuthoritySubscription.po.js b/e2e/pages/EnableAuthoritySubscription.po.js index bd83bfe439..39664def50 100644 --- a/e2e/pages/EnableAuthoritySubscription.po.js +++ b/e2e/pages/EnableAuthoritySubscription.po.js @@ -6,7 +6,7 @@ class EnableAuthoritySubscription { } async skipStep(languageStrings) { - await element(by.text(languageStrings.label.skip_this_step).tap()); + await element(by.text(languageStrings.label.skip_this_step)).tap(); } async takeScreenshot() { From 2b346de1bd4d949cc1d95ba469555fc49910207b Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Tue, 28 Apr 2020 07:55:02 -0700 Subject: [PATCH 41/60] Remove react-native-maps (#695) --- android/app/src/main/AndroidManifest.xml | 3 --- ios/COVIDSafePaths.xcodeproj/project.pbxproj | 2 -- ios/Podfile.lock | 6 ------ package.json | 1 - yarn.lock | 5 ----- 5 files changed, 17 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d6e6a858e9..186061acdf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -49,9 +49,6 @@ - Date: Tue, 28 Apr 2020 12:18:23 -0700 Subject: [PATCH 42/60] Fix i18n placeholder for authorities (#709) --- app/locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/locales/en.json b/app/locales/en.json index a098560158..8a230a2c91 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -42,8 +42,8 @@ "authorities_new_in_area_title_plural": "New Healthcare Authorities", "authorities_new_subcription_msg": "You have been subscribed to {{count}} new authority in your location", "authorities_new_subcription_msg_plural": "You have been subscribed to {{count}} new authorities in your location", - "authorities_new_subcription_title": "{{numAuthories}} New Subscription", - "authorities_new_subcription_title_plural": "{{numAuthories}} New Subscriptions", + "authorities_new_subcription_title": "{{count}} New Subscription", + "authorities_new_subcription_title_plural": "{{count}} New Subscriptions", "authorities_no_sources": "No data source yet", "authorities_removal_alert_cancel": "Cancel", "authorities_removal_alert_desc": "Are you sure you want to remove this authority data source?", From b4e034f071b0a9a95b5fd9867424be40af85e5ef Mon Sep 17 00:00:00 2001 From: kpugsley Date: Tue, 28 Apr 2020 14:24:08 -0500 Subject: [PATCH 43/60] remove default news url from News.js constructor (#704) Co-authored-by: Ken Pugsley <> --- app/views/News.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/News.js b/app/views/News.js index 7a7158f2a7..deb891156a 100644 --- a/app/views/News.js +++ b/app/views/News.js @@ -33,7 +33,7 @@ class NewsScreen extends Component { this.state = { visible: true, default_news: default_news, - newsUrls: [default_news, default_news], + newsUrls: [], current_page: 0, }; } From 1ea12aa45b7f778ca250d3526a05ad51344a4513 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Tue, 28 Apr 2020 20:41:35 -0500 Subject: [PATCH 44/60] Put auto sub behind dev flags (#711) Signed-off-by: Patrick Erichsen --- app/services/BackgroundTaskService.js | 2 +- app/views/ChooseProvider.js | 16 +++++---- app/views/onboarding/Onboarding5.js | 20 +++++++---- e2e/helpers/onboarding.js | 14 +------- .../NoAuthSubscriptionPermission.spec.js | 35 ------------------- 5 files changed, 25 insertions(+), 62 deletions(-) delete mode 100644 e2e/onboarding/NoAuthSubscriptionPermission.spec.js diff --git a/app/services/BackgroundTaskService.js b/app/services/BackgroundTaskService.js index 529ea4085c..ccf661068f 100644 --- a/app/services/BackgroundTaskService.js +++ b/app/services/BackgroundTaskService.js @@ -6,7 +6,7 @@ import { HCAService } from '../services/HCAService'; export function executeTask() { checkIntersect(); - HCAService.findNewAuthorities(); + __DEV__ && HCAService.findNewAuthorities(); } export default class BackgroundTaskServices { diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index ce0b1c5191..77cc440883 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -235,13 +235,15 @@ class ChooseProviderScreen extends Component { {languages.t('label.authorities_desc')} - - this.toggleAutoSubscribe()} - /> - + {__DEV__ && ( + + this.toggleAutoSubscribe()} + /> + + )} diff --git a/app/views/onboarding/Onboarding5.js b/app/views/onboarding/Onboarding5.js index a6b17c83c7..76d46ed5b3 100644 --- a/app/views/onboarding/Onboarding5.js +++ b/app/views/onboarding/Onboarding5.js @@ -83,7 +83,7 @@ class Onboarding extends Component { componentDidMount() { this.checkLocationStatus(); isPlatformiOS() && this.checkNotificationStatus(); - this.checkSubsriptionStatus(); + __DEV__ && this.checkSubsriptionStatus(); } isLocationChecked() { @@ -106,11 +106,9 @@ class Onboarding extends Component { getNextStep(currentStep) { switch (currentStep) { case StepEnum.LOCATION: - return isPlatformiOS() - ? StepEnum.NOTIFICATIONS - : StepEnum.HCA_SUBSCRIPTION; + return this.getLocationNextStep(); case StepEnum.NOTIFICATIONS: - return StepEnum.HCA_SUBSCRIPTION; + return __DEV__ ? StepEnum.HCA_SUBSCRIPTION : StepEnum.DONE; case StepEnum.HCA_SUBSCRIPTION: return StepEnum.DONE; } @@ -175,6 +173,16 @@ class Onboarding extends Component { } } + getLocationNextStep() { + if (isPlatformiOS()) { + return StepEnum.NOTIFICATIONS; + } else if (__DEV__) { + return StepEnum.HCA_SUBSCRIPTION; + } else { + return isPlatformiOS() ? StepEnum.NOTIFICATIONS : StepEnum.DONE; + } + } + /** * Gets the respective location permissions settings string * for the user's current device. @@ -427,7 +435,7 @@ class Onboarding extends Component { {this.getLocationPermission()} {this.getNotificationsPermissionIfIOS()} - {this.getAuthSubscriptionStatus()} + {__DEV__ && this.getAuthSubscriptionStatus()} diff --git a/e2e/helpers/onboarding.js b/e2e/helpers/onboarding.js index afa2982ef5..390f2eb3e7 100644 --- a/e2e/helpers/onboarding.js +++ b/e2e/helpers/onboarding.js @@ -23,20 +23,8 @@ export const navigateThroughPermissions = async languageStrings => { await Onboarding4.tapButton(languageStrings); }; -export const navigateThroughOnboarding = async ( - languageStrings, - isAutoSubcribe = true, -) => { +export const navigateThroughOnboarding = async languageStrings => { await navigateThroughPermissions(languageStrings); - - await EnableAuthoritySubscription.isOnScreen(languageStrings); - - if (isAutoSubcribe) { - await EnableAuthoritySubscription.enable(languageStrings); - } else { - await EnableAuthoritySubscription.skipStep(languageStrings); - } - await FinishSetup.isOnScreen(languageStrings); await FinishSetup.takeScreenshot(languageStrings); await FinishSetup.tapButton(languageStrings); diff --git a/e2e/onboarding/NoAuthSubscriptionPermission.spec.js b/e2e/onboarding/NoAuthSubscriptionPermission.spec.js deleted file mode 100644 index 6ee32db090..0000000000 --- a/e2e/onboarding/NoAuthSubscriptionPermission.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable jest/expect-expect */ -import { languages } from '../helpers/language'; -import { navigateThroughOnboarding } from '../helpers/onboarding'; -import Home from '../pages/Home.po.js'; - -describe.each(languages)( - `Healthcare Authority auto subscription in %s`, - (locale, languageStrings) => { - beforeAll(async () => { - await device.launchApp({ - newInstance: true, - languageAndLocale: { - language: locale, - locale, - }, - permissions: { location: 'always', notifications: 'YES' }, - }); - }); - - describe('When skipping the auto subscribe step', () => { - beforeAll(async () => { - await navigateThroughOnboarding(languageStrings); - }); - - it('Shows auto subscribe as unset', async () => { - await Home.takeScreenshot(); - }); - }); - - afterAll(async () => { - await device.uninstallApp(); - await device.installApp(); - }); - }, -); From 721eaee1a102c2bcb37d95ca6238df5ea1c65bdf Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Tue, 28 Apr 2020 19:12:57 -0700 Subject: [PATCH 45/60] Fix headline1 text cutoff (#712) --- app/components/Typography.js | 2 +- app/components/__tests__/__snapshots__/Typography.spec.js.snap | 2 +- app/constants/__tests__/__snapshots__/Theme.spec.js.snap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/Typography.js b/app/components/Typography.js index 13894dfab9..bda5e4ab9a 100644 --- a/app/components/Typography.js +++ b/app/components/Typography.js @@ -79,7 +79,7 @@ const FONT_SIZE_MAP = { const getFontSize = ({ use = Type.Body1 }) => FONT_SIZE_MAP[use]; const LINE_HEIGHT_MAP = { - [Type.Headline1]: '48px', + [Type.Headline1]: '52px', [Type.Headline2]: '34px', [Type.Headline3]: '40px', [Type.Body1]: '24px', diff --git a/app/components/__tests__/__snapshots__/Typography.spec.js.snap b/app/components/__tests__/__snapshots__/Typography.spec.js.snap index 7a4c823e04..9bb84b34fc 100644 --- a/app/components/__tests__/__snapshots__/Typography.spec.js.snap +++ b/app/components/__tests__/__snapshots__/Typography.spec.js.snap @@ -88,7 +88,7 @@ exports[`headline1 is large and bold 1`] = ` "fontFamily": "IBMPlexSans-Bold", "fontSize": 52, "fontWeight": "bold", - "lineHeight": 48, + "lineHeight": 52, "writingDirection": "ltr", } } diff --git a/app/constants/__tests__/__snapshots__/Theme.spec.js.snap b/app/constants/__tests__/__snapshots__/Theme.spec.js.snap index 2e4ad5a566..3c7d71feca 100644 --- a/app/constants/__tests__/__snapshots__/Theme.spec.js.snap +++ b/app/constants/__tests__/__snapshots__/Theme.spec.js.snap @@ -67,7 +67,7 @@ exports[`includes extra background View if setBackground=true 1`] = ` "fontFamily": "IBMPlexSans-Bold", "fontSize": 52, "fontWeight": "bold", - "lineHeight": 48, + "lineHeight": 52, "writingDirection": "ltr", } } From 939ca28ec0b70b418e33a53a39fe95fff0c210d4 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Tue, 28 Apr 2020 21:40:23 -0500 Subject: [PATCH 46/60] Put GPS filter behind dev flag (#714) Signed-off-by: Patrick Erichsen --- app/views/ChooseProvider.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/app/views/ChooseProvider.js b/app/views/ChooseProvider.js index 77cc440883..8536d568a8 100644 --- a/app/views/ChooseProvider.js +++ b/app/views/ChooseProvider.js @@ -42,7 +42,7 @@ class ChooseProviderScreen extends Component { urlEntryInProgress: false, urlText: '', authoritiesList: [], - isAuthorityFilterActive: true, + isAuthorityFilterActive: false, isAutoSubscribed: false, }; } @@ -60,7 +60,7 @@ class ChooseProviderScreen extends Component { BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); await this.fetchAuthoritiesList(this.state.isAuthorityFilterActive); await this.fetchUserAuthorities(); - await this.fetchAutoSubcribeStatus(); + __DEV__ && (await this.fetchAutoSubcribeStatus()); } componentWillUnmount() { @@ -345,19 +345,21 @@ class ChooseProviderScreen extends Component { - this.toggleFilterAuthoritesByGPSHistory()}> - - {languages.t('label.filter_authorities_by_gps_history')} - - - this.filterAuthoritesByGPSHistory({ val }) - } - value={this.state.isAuthorityFilterActive} - /> - + {__DEV__ && ( + this.toggleFilterAuthoritesByGPSHistory()}> + + {languages.t('label.filter_authorities_by_gps_history')} + + + this.filterAuthoritesByGPSHistory({ val }) + } + value={this.state.isAuthorityFilterActive} + /> + + )} {this.state.authoritiesList === undefined ? null : this.state.authoritiesList.map(item => { From c3e7a7e015ecdb4476fbf27a6348ed39bb721383 Mon Sep 17 00:00:00 2001 From: kpugsley Date: Tue, 28 Apr 2020 22:30:00 -0500 Subject: [PATCH 47/60] 1.0.1 (#715) Co-authored-by: Ken Pugsley <> --- android/app/build.gradle | 4 ++-- ios/COVIDSafePaths/Info.plist | 4 ++-- ios/COVIDSafePathsTests/Info.plist | 4 ++-- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b7d1f1143f..3d35299108 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -137,8 +137,8 @@ android { testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - versionCode 24 - versionName "1.0.0" + versionCode 25 + versionName "1.0.1" } splits { abi { diff --git a/ios/COVIDSafePaths/Info.plist b/ios/COVIDSafePaths/Info.plist index 36bbff8ad8..5694700779 100644 --- a/ios/COVIDSafePaths/Info.plist +++ b/ios/COVIDSafePaths/Info.plist @@ -27,11 +27,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + 1.0.1 CFBundleSignature ???? CFBundleVersion - 24 + 25 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/ios/COVIDSafePathsTests/Info.plist b/ios/COVIDSafePathsTests/Info.plist index 11d950655a..1dab95c146 100644 --- a/ios/COVIDSafePathsTests/Info.plist +++ b/ios/COVIDSafePathsTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0.0 + 1.0.1 CFBundleSignature ???? CFBundleVersion - 24 + 25 diff --git a/package.json b/package.json index 693ee8b794..d8b83d9d09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "covidsafepaths", - "version": "1.0.0", + "version": "1.0.1", "private": true, "scripts": { "start": "yarn && react-native start", From 4a334e002fff233965c011ec6fa684a8cdb4f5f7 Mon Sep 17 00:00:00 2001 From: kpugsley Date: Wed, 29 Apr 2020 11:37:26 -0500 Subject: [PATCH 48/60] update the upgrade version to 1.0.0, to match what is in the master branch and avoid mistaken forced upgrades. (#723) Co-authored-by: Ken Pugsley <> --- versions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.yaml b/versions.yaml index 4b23c9a500..b451081fda 100644 --- a/versions.yaml +++ b/versions.yaml @@ -1,4 +1,4 @@ # Versions less than specified will receive a gentle update notification (dismissable alert only) -upgrade version: "0.9.5" +upgrade version: "1.0.0" # Versions less than specified will receive a mandatory update notification (push + non-dismissable alert) mandatory upgrade version: "0.9.4" From 222f28055af898fe8c3e958d08ffe3bd7d449d6d Mon Sep 17 00:00:00 2001 From: deedos Date: Wed, 29 Apr 2020 13:38:39 -0700 Subject: [PATCH 49/60] Fix isVersionGreater and add tests (#726) --- app/Util.js | 6 ++---- app/__tests__/Util.spec.js | 27 +++++++++++++++++++++++++++ package.json | 1 + yarn.lock | 5 +++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 app/__tests__/Util.spec.js diff --git a/app/Util.js b/app/Util.js index d6ac48d3e5..e181ab576c 100644 --- a/app/Util.js +++ b/app/Util.js @@ -1,5 +1,6 @@ import dayjs from 'dayjs'; import { Platform } from 'react-native'; +import semver from 'semver'; export function isPlatformiOS() { return Platform.OS === 'ios'; @@ -13,10 +14,7 @@ export function nowStr() { return dayjs().format('H:mm'); } -export const isVersionGreater = (source, target) => - source - .split('.') - .some((val, index) => parseInt(val) > parseInt(target.split('.')[index])); +export const isVersionGreater = (source, target) => semver.gt(source, target); export default { isPlatformiOS, diff --git a/app/__tests__/Util.spec.js b/app/__tests__/Util.spec.js new file mode 100644 index 0000000000..eae016ce7e --- /dev/null +++ b/app/__tests__/Util.spec.js @@ -0,0 +1,27 @@ +import { isVersionGreater } from '../Util'; + +describe('Util class', () => { + let versions; + + beforeEach(() => { + versions = [ + ['0.0.1', '0.0.0', true], + ['1.0.1', '0.9.9', true], + ['3.0.0', '2.7.2+asdf', true], + ['1.2.3-a.10', '1.2.3-a.5', true], + ['1.2.3-a.b', '1.2.3-a', true], + ['0.9.5', '1.0.1', false], + ['0.9.9', '1.0.0', false], + ['1.3.4', '1.3.5', false], + ['2.9.1', '3.0.0', false], + ['1.2.3', '1.2.3', false], + ]; + }); + + it('version greater than works', () => { + versions.forEach(([v0, v1, expectedValue]) => { + const result = isVersionGreater(v0, v1); + expect(result).toBe(expectedValue); + }); + }); +}); diff --git a/package.json b/package.json index d8b83d9d09..dd57f4e630 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "react-native-zip-archive": "^5.0.1", "reanimated-bottom-sheet": "^1.0.0-alpha.19", "rn-fetch-blob": "^0.12.0", + "semver": "^7.3.2", "victory-native": "^34.1.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index b288234cbc..6582115f1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8536,6 +8536,11 @@ semver@^7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" From 285b38313081c59d74e3c7f1d183ae69335b2474 Mon Sep 17 00:00:00 2001 From: Penske Williano <30844375+vestial@users.noreply.github.com> Date: Thu, 30 Apr 2020 04:10:56 +0200 Subject: [PATCH 50/60] Fix postinstall script on Windows (#720) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd57f4e630..1df18683c8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"", "install:pod": "cd ios && bundle install && bundle exec pod install", "lint": "eslint ./", - "postinstall": "patch-package; npx react-native-jetifier", + "postinstall": "patch-package && npx react-native-jetifier", "postversion": "react-native-version", "format:all": "prettier --write ./app/**/*.js", "detox-setup": "detox clean-framework-cache && detox build-framework-cache", From e063015a63404a3ab192649ad16f2aaa2baa7380 Mon Sep 17 00:00:00 2001 From: Krishna <503055+Krish023@users.noreply.github.com> Date: Thu, 30 Apr 2020 08:03:49 +0530 Subject: [PATCH 51/60] App shows green icon when tracking is inactive (#722) --- app/views/LocationTracking.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/views/LocationTracking.js b/app/views/LocationTracking.js index bb0a458e07..6cfe636a97 100644 --- a/app/views/LocationTracking.js +++ b/app/views/LocationTracking.js @@ -140,13 +140,26 @@ class LocationTracking extends Component { checkIfUserAtRisk() { BackgroundTaskServices.start(); - + // If the user has location tracking disabled, set enum to match + GetStoreData(PARTICIPATE, false).then(isParticipating => { + if (isParticipating === false) { + this.setState({ + currentState: StateEnum.SETTING_OFF, + }); + } + //Location enable + else { + this.crossPathCheck(); + } + }); + } + //Due to Issue 646 moved below code from checkIfUserAtRisk function + crossPathCheck() { GetStoreData(DEBUG_MODE).then(dbgMode => { if (dbgMode != 'true') { // already set on 12h timer, but run when this screen opens too checkIntersect(); } - GetStoreData(CROSSED_PATHS).then(dayBin => { dayBin = JSON.parse(dayBin); if (dayBin !== null && dayBin.reduce((a, b) => a + b, 0) > 0) { @@ -158,15 +171,6 @@ class LocationTracking extends Component { } }); }); - - // If the user has location tracking disabled, set enum to match - GetStoreData(PARTICIPATE, false).then(isParticipating => { - if (isParticipating === false) { - this.setState({ - currentState: StateEnum.SETTING_OFF, - }); - } - }); } componentDidMount() { From 9b2c5b64706c83bfd2ec6ea34bbe5fb2b07b8df4 Mon Sep 17 00:00:00 2001 From: denispapakul <47609057+denispapakul@users.noreply.github.com> Date: Thu, 30 Apr 2020 07:01:01 +0300 Subject: [PATCH 52/60] Set up location after skiping the steps (#721) --- app/views/onboarding/Onboarding5.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/onboarding/Onboarding5.js b/app/views/onboarding/Onboarding5.js index 76d46ed5b3..511ec192a7 100644 --- a/app/views/onboarding/Onboarding5.js +++ b/app/views/onboarding/Onboarding5.js @@ -287,7 +287,10 @@ class Onboarding extends Component { this.requestHCASubscription(); break; case StepEnum.DONE: - SetStoreData(PARTICIPATE, 'true'); + SetStoreData( + PARTICIPATE, + this.state.locationPermission === PermissionStatusEnum.GRANTED, + ); SetStoreData('ONBOARDING_DONE', true); this.props.navigation.replace('LocationTrackingScreen'); } From 16d52c60a9ce36be813e89db1366df48ef1b5426 Mon Sep 17 00:00:00 2001 From: Matteo Crippa Date: Thu, 30 Apr 2020 17:44:29 +0200 Subject: [PATCH 53/60] updated issue template (#729) --- .github/ISSUE_TEMPLATE.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 694c0ca763..ba8bc983e8 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,11 +2,27 @@ -## Version -- Type of phone: Android / iPhone -- OS Version: -- Model: e.g. Samsung Galaxy S8 + + + + + + +## Issue type + + +## Version +- Release build version: + +## Affected Devices + +| Brand | Model | OS | +| ------------ |:-------------:| ----:| + + +## Affected Languages + ## Steps to reproduce From 6ce9fe1df3868c7a553168e042fd50014069ddc5 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 30 Apr 2020 09:33:25 -0700 Subject: [PATCH 54/60] Translation refresh (#708) --- .../app/src/main/res/values-it/strings.xml | 2 +- .../app/src/main/res/values-pl/strings.xml | 5 + .../app/src/main/res/values-ru/strings.xml | 5 + .../app/src/main/res/values-sk/strings.xml | 5 + android/app/src/main/res/values/strings.xml | 6 +- app/locales/ar.json | 70 ++------- app/locales/fil.json | 12 +- app/locales/ht.json | 10 ++ app/locales/it.json | 61 +++++--- app/locales/ml.json | 29 +++- app/locales/pl.json | 137 +++++++++++------- app/locales/pull.sh | 3 +- app/locales/ru.json | 27 ++++ app/locales/sk.json | 57 +++++++- ios/en.lproj/Localizable.strings | 2 +- ios/it.lproj/Localizable.strings | 2 +- ios/pl.lproj/InfoPlist.strings | 5 + ios/pl.lproj/Localizable.strings | 4 + ios/sk.lproj/InfoPlist.strings | 5 + ios/sk.lproj/Localizable.strings | 4 + 20 files changed, 309 insertions(+), 142 deletions(-) create mode 100644 android/app/src/main/res/values-pl/strings.xml create mode 100644 android/app/src/main/res/values-ru/strings.xml create mode 100644 android/app/src/main/res/values-sk/strings.xml create mode 100644 ios/pl.lproj/InfoPlist.strings create mode 100644 ios/pl.lproj/Localizable.strings create mode 100644 ios/sk.lproj/InfoPlist.strings create mode 100644 ios/sk.lproj/Localizable.strings diff --git a/android/app/src/main/res/values-it/strings.xml b/android/app/src/main/res/values-it/strings.xml index d3091d899f..634d728a24 100644 --- a/android/app/src/main/res/values-it/strings.xml +++ b/android/app/src/main/res/values-it/strings.xml @@ -1,5 +1,5 @@ - Gestisci tutte le notifiche locali. + Gestisci tutte le notifiche locali diff --git a/android/app/src/main/res/values-pl/strings.xml b/android/app/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..d1351dce8d --- /dev/null +++ b/android/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,5 @@ + + + + Obsługa wszystkich lokalnych powiadomień + diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..9ab53c6fac --- /dev/null +++ b/android/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,5 @@ + + + + Настроить местные оповещения + diff --git a/android/app/src/main/res/values-sk/strings.xml b/android/app/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..77f30a4ca2 --- /dev/null +++ b/android/app/src/main/res/values-sk/strings.xml @@ -0,0 +1,5 @@ + + + + Safe Paths upozornenia + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 74bbec0e43..09ac63abc2 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,9 +1,9 @@ - COVID Safe Paths + COVID Safe Paths - Safe Paths + Safe Paths - Handle all local notifications + Safe Paths notifications diff --git a/app/locales/ar.json b/app/locales/ar.json index d8b133686c..39b630b544 100644 --- a/app/locales/ar.json +++ b/app/locales/ar.json @@ -1,5 +1,14 @@ { "_display_name": "العربية", + "history": { + "no_exposure": "لم يتم التعرض", + "possible_exposure": "التعرض محتمل", + "possible_exposure_para": "من الممكن أن تكون قد تعرضت من شخص مصاب بمرض COVID-19 أو كنت قريبًا منه", + "what_does_this_mean": "ماذا يعني هذا؟", + "what_does_this_mean_para": "استنادًا إلى سجل نظام تحديد المواقع العالمي (GPS) ، من المحتمل أنك قد تكون تعرضت أو قريب لشخص تم تشخيصه بـ COVID-19. هذا لا يعني أنك مصاب ولكن قد تكون مصابًا. \n \n لمزيد من المعلومات حول ما يجب عليك القيام به ، يمكنك الرجوع إلى موقع Mayo Clinic الإلكتروني.", + "what_if_no_symptoms": "ماذا لو لم تظهر الأعراض؟", + "what_if_no_symptoms_para": "إذا لم يكن لديك أعراض ولكنك لا تزال ترغب في الاختبار ، يمكنك الذهاب إلى أقرب موقع اختبار. \n \n يمكن للأفراد الذين لا تظهر عليهم الأعراض أحيانًا حمل العدوى وإصابة الآخرين. إن توخي الحذر بشأن التباعد الاجتماعي والتواصل مع مجموعات كبيرة أو الأفراد المعرضين للخطر (كبار السن ، والذين يعانون من مشاكل طبية أخرى مهمة) مهم لإدارة كل من المخاطر الخاصة بك والمخاطر التي يتعرض لها الآخرون." + }, "label": { "about_title": "نبذة عنا", "authorities_add_button_label": "إضافة هيئة صحية موثوقة", @@ -19,30 +28,18 @@ "default_news_site_name": "أخبار المسارات الآمنة", "event_history_subtitle": "افهم تعرضك الشخصي بناءً على المعلومات التي تشاركها السلطات الصحية", "event_history_title": "سجلات التعرض", - "export_para_1": "إذا كانت نتيجة اختبار COVID-19 إيجابية ، فالرجاء القيام بدورك من خلال مشاركة سجل المواقع مع الهيئات المحلية", - "export_para_2": "تتم مشاركة بيانات الموقع كقائمة بسيطة بالأوقات والأماكن ، ولا توجد معلومات إضافية", "home_at_risk_header": "ربما تكون قد تعرضت", "home_at_risk_subsubtext": "هذا لا يعني أنك مصاب", - "home_at_risk_subtext": "استنادًا إلى سجل GPS الخاص بك ، من المحتمل أنك كنت على اتصال أو قريبًا من شخص تم تشخيصه بـ COVID19", + "home_at_risk_subtext": "استنادًا إلى سجل GPS الخاص بك ، من المحتمل أنك كنت على اتصال أو قريبًا من شخص تم تشخيصه بـ COVID-19", "home_enable_location": "تفعيل خدمة الموقع", "home_mayo_link_heading": "مزيد من المعلومات عن COVID-19", "home_mayo_link_label": " من (مايو كلينك) Mayo Clinic ", "home_no_contact_header": "لم يتم التعرض", "home_no_contact_subtext": "استنادًا إلى البيانات المتاحة ، لم تكن بالقرب من أي شخص تم الإبلاغ عنه إيجابيًا بـ COVID-19", - "home_unknown_header": "غير معروف", - "home_unknown_subtext": "لا يمكننا معرفة ما إذا كنت معرضًا للخطر ما لم يكن للتطبيق حق الوصول إلى بيانات موقعك", "home_setting_off_header": "غير معروف", "home_setting_off_subtext": "لا يمكننا معرفة ما إذا كنت في خطر ما لم تقم بتفعيل سجل المواقع المتتبعة الموجودة في شاشة الإعدادات", - "import_title": "تحميل المواقع المتعقبة", - "import_step_1": "ستمنحك إضافة بيانات موقعك من Google السبق في بناء مواقعك الأخيرة", - "import_step_2": "قبل أن تتمكن من التحميل ، يجب عليك أولاً الحصول على بيانات موقعك من Google", - "import_step_3": "قم بزيارة Google Takeout وقم بتحميل سجل المواقع باستخدام الإعدادات التالية: \n1. طريقة التحميل: \"الإضافة إلى Google Drive \" \n2. التكرار: \"التحميل مرة واحدة \" \n3. نوع الملف وحجمه: \". zip \" و \"1 جيجابايت \" \n4. ترسل Google بريدًا إلكترونيًا عندما يكون التحميل جاهزًا \n5. عد هنا لتحميل المواقع. خيارات التحميل: \n- التحميل من Google Drive \n- التحميل من المتصفح ، ثم التحميل من ملفات الهاتف المحلية. تأكد من أن تكون على شبكة WiFi لأن الملفات يمكن أن تكون كبيرة.", - "import_takeout": "قم بزيارة Google Takeout", - "import_success": "تم تحميل المواقع الأخيرة بنجاح!", - "import_already_imported": "تم تحميل ملف Takeout المقدم بالفعل", - "import_error": "حدث خطأ أثناء تحميل بياناتك", - "import_no_recent_locations": "ليس لدى Takeout أي مواقع حديثة", - "import_invalid_file_format": "تنسيق الملف المقدم غير معتمد. \n التنسيقات المدعومة: \". zip \".", + "home_unknown_header": "غير معروف", + "home_unknown_subtext": "لا يمكننا معرفة ما إذا كنت معرضًا للخطر ما لم يكن للتطبيق حق الوصول إلى بيانات موقعك", "latest_news": "أحدث الأخبار", "launch_done_header": "انتهينا", "launch_done_subheader": "أنت على استعداد لبدء التنفيذ.\n تذكر ، يمكنك دائمًا تحديث تفضيلاتك لاحقًا", @@ -66,59 +63,22 @@ "launch_screen4_subheader": "إذا كان الاختبار إيجابيًا ، انت فقط يمكنك اختيار المشاركة", "launch_set_up_phone": "إعداد هاتفي", "legal_page_title": "النظام القانوني", - "less_than_one_minute": "أقل من دقيقة واحدة", "loading_public_data": "جاري تحميل البيانات...", "location_disabled_message": "يتطلب تطبيق COVID Safe Paths خدمات الموقع", "location_disabled_title": "تم تعطيل خدمة تتبع الموقع", "location_enabled_message": "يقوم تطبيق COVID Safe Paths بتخزين إحداثيات موقعك بأمان مرة واحدة كل خمس دقائق على هذا الجهاز", "location_enabled_title": "تم تفعيل تطبيق COVID Safe Paths", - "maps_import_button_text": "تحميل المواقع المتعقبة السابقة", - "maps_import_disclaimer": "لا يرتبط تطبيق Safe Paths بـ Google ولا يشارك بياناتك مطلقًا", - "maps_import_text": "لمعرفة ما إذا كنت قد تعرفت على شخص لديه COVID-19 قبل تحميل هذا التطبيق ، يمكنك استيراد سجل موقعك الشخصي", - "maps_import_title": "خرائط جوجل", - "nCoV2019_url_info": "لمزيد من المعلومات حول مجموعة البيانات لهذه الخريطة", + "logging_active": " خدمة تتبع الموقع مفعلة", + "logging_inactive": "خدمة تتبع الموقع غير مفعلة", "news_subtitle": "اقرأ عن آخر تحديثات COVID من السلطات الصحية الخاصة بك وبشكل عام", "news_title": "أحدث الأخبار", "no_data": "لا يوجد بيانات", - "notification_2_weeks_ago": "قبل اسبوعين", - "notification_data_not_available": "لا توجد بيانات تعرض متاحة", - "notification_select_authority": "حدد هيئة الرعاية الصحية", - "notification_title": "الملف التعريفي للتعرض لمدة أسبوعين", - "notification_today": "اليوم", - "notification_warning_text": "في حالة وجود هيئة رعاية صحية في منطقتك ، يمكنك الاشتراك للحصول على تحديثات منتظمة لمخاطر التعرض", - "notifications_exposure_format": "قبل {{daysAgo}} يومًا ، تعرّضت لشخص معدي لمدة {{exposureTime}} دقيقة", - "notifications_exposure_format_today": "لقد تعرّضت اليوم لشخص معدي لمدة {{exposureTime}} دقيقة", - "notifications_exposure_format_yesterday": "لقد تعرّضت أمس لشخص معدي لمدة {{exposureTime}} دقيقة", - "notifications_no_exposure": "لم يتم التعرض لـ COVID-19 خلال الأسبوعين الماضيين", - "overlap_found_button_label": "تم تحميل البيانات العامة", - "overlap_no_results_button_label": "تم تحميل البيانات العامة", - "overlap_para_1": "يمثل الممر الأخضر سجل موقعك \n \n تمثل الدوائر ذات اللون الأرجواني الفاتح مجموعة البيانات العامة", - "overlap_title": "تحقق من التداخل", "push_at_risk_message": "لقد عبرت بجانب مريض COVID-19", "push_at_risk_title": "قد تكون في خطر", "see_exposure_history": "إظهار بيانات التعرض", "settings_title": "لوحة القيادة", - "share_location_data": "مشاركة بيانات تتبع الموقع", - "show_overlap": "انقر لعرض مجموعة البيانات العامة", "team": "الفريق", "team_para": "يتكون فريقنا من مجموعة من علماء الأوبئة والمهندسين وعلماء البيانات وأخصائيين الخصوصية الرقمية والأساتذة والباحثين من المؤسسات المرموقة ، بما في ذلك: MIT و Harvard و The Mayo Clinic و TripleBlind و EyeNetra و Ernst & Young و Link Ventures", - "terms_of_use": "تعليمات الاستخدام", - "tested_positive_subtitle": "يمكن نقل بياناتك الخاصة إلى السلطات الصحية أو نسخها احتياطيًا أو مشاركتها بأي طريقة أخرى", - "tested_positive_title": "مشاركة سجل تتبع الموقع", - "logging_active": " خدمة تتبع الموقع مفعلة", - "logging_inactive": "خدمة تتبع الموقع غير مفعلة", - "language_change_alert_title": "يحتاج التطبيق إلى إعادة التشغيل لاستخدام هذه اللغة", - "language_change_alert_proced": "إعادة التشغيل", - "language_change_alert_cancel": "إلغاء" - }, - "history": { - "no_exposure": "لم يتم التعرض", - "possible_exposure_para": "من الممكن أن تكون قد تعرضت من شخص مصاب بمرض COVID-19 أو كنت قريبًا منه", - "possible_exposure": "التعرض محتمل", - "timeline": "الجدول الزمني", - "what_does_this_mean_para": "استنادًا إلى سجل نظام تحديد المواقع العالمي (GPS) ، من المحتمل أنك قد تكون تعرضت أو قريب لشخص تم تشخيصه بـ COVID19. هذا لا يعني أنك مصاب ولكن قد تكون مصابًا. \n \n لمزيد من المعلومات حول ما يجب عليك القيام به ، يمكنك الرجوع إلى موقع Mayo Clinic الإلكتروني.", - "what_does_this_mean": "ماذا يعني هذا؟", - "what_if_no_symptoms_para": "إذا لم يكن لديك أعراض ولكنك لا تزال ترغب في الاختبار ، يمكنك الذهاب إلى أقرب موقع اختبار. \n \n يمكن للأفراد الذين لا تظهر عليهم الأعراض أحيانًا حمل العدوى وإصابة الآخرين. إن توخي الحذر بشأن التباعد الاجتماعي والتواصل مع مجموعات كبيرة أو الأفراد المعرضين للخطر (كبار السن ، والذين يعانون من مشاكل طبية أخرى مهمة) مهم لإدارة كل من المخاطر الخاصة بك والمخاطر التي يتعرض لها الآخرون.", - "what_if_no_symptoms": "ماذا لو لم تظهر الأعراض؟" + "terms_of_use": "تعليمات الاستخدام" } } diff --git a/app/locales/fil.json b/app/locales/fil.json index c66f1f939c..2c4efc0b41 100644 --- a/app/locales/fil.json +++ b/app/locales/fil.json @@ -37,10 +37,18 @@ "launch_notif_header": "Kayo ay makakatanggao ng abiso o paalala kung sakali mang kayo ay may nakahalubilo na maysakit", "launch_notif_subheader": "Ang aming hihilingin lang sa inyo bilang abala ay kung may panganib, maari po lamang na ito ay ibahagi nyo sa amin.", "launch_notification_access": "Payagan ang mga notipikasyon", - "launch_screen2_header": "Iyong maabisuhan kung ikaw ay cross landas na may isang tao sa ibang pagkakataon nasuri para sa COVID-19." + "launch_screen2_header": "Iyong maabisuhan kung ikaw ay cross landas na may isang tao sa ibang pagkakataon nasuri para sa COVID-19.", + "launch_screen2_subheader": "Ang kaalaman ay kapangyarihan.", + "launch_screen3_header": "Kung ikaw ay kumpirmadong may COVID-19, maaari mong ibahagi sa amin ang iyong detalye nang di nagpapakilala.", + "launch_screen3_subheader": "Ito ay makakatulong upang mapanatiling ligtas ang ating komunidad.", + "legal_page_title": "Legal", + "loading_public_data": "Naglo-load ng datos ...", + "news_title": "Pinakabagong balita", + "no_data": "Walang datos", + "settings_title": "Dashboard" }, "onboarding": { - "eula_checkbox": "Tinatanggap ko ang kasunduan nito.", + "eula_checkbox": "Tinatanggap ko ang kasunduan ito.", "eula_continue": "Ituloy" }, "version_update": { diff --git a/app/locales/ht.json b/app/locales/ht.json index 05f3aa8f82..c056183142 100644 --- a/app/locales/ht.json +++ b/app/locales/ht.json @@ -1,5 +1,10 @@ { "_display_name": "Kreyòl ayisyen", + "about": { + "dimensions": "Dimansyon:", + "operating_system_abbr": "OS:", + "version": "Vesyon:" + }, "history": { "no_exposure": "Pa gen kontak", "possible_exposure": "Posib", @@ -91,6 +96,11 @@ "team_para": "Ekip nou an konpoze de yon group ki genyen ladanl epidemyolojis, enjenyè, syantis done, evanjeliS vi prive dijital, pwofesè ak chèchè nan anpil gran enstitisyon nan mond lan, tankou: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young ak Link Ventures.", "terms_of_use": "Règleman itilizasyon" }, + "onboarding": { + "eula_checkbox": "Mwen aksepte akò lisans la", + "eula_continue": "Kontinye", + "eula_message": "* Ou dwe aksepte pou itilize Safe Paths 'Chase Kowona" + }, "share": { "button_text": "Pataje done lokalizasyon", "paragraph_first": "Si ou teste pozitif pou COVID-19, tanpri fè pati pa ou nan pataje list pozisyon ou ak otorite lokal yo.", diff --git a/app/locales/it.json b/app/locales/it.json index 533e6bdfda..69fa44e69d 100644 --- a/app/locales/it.json +++ b/app/locales/it.json @@ -1,22 +1,33 @@ { "_display_name": "Italiano", + "about": { + "dimensions": "Dimensioni Schermo:", + "operating_system_abbr": "Sistema Operativo:", + "version": "Versione:" + }, + "common": { + "done": "Fatto" + }, "history": { "no_exposure": "Non noto", "possible_exposure": "Possibile", - "possible_exposure_para": "E' possibile che tu sia stato in contatto o vicino ad una persona risultata positiva al COVID-19.", + "possible_exposure_para": "È possibile che tu sia stato in contatto o vicino a una persona risultata positiva al COVID-19.", "what_does_this_mean": "Cosa significa?", "what_does_this_mean_para": "In base alla tua cronologia GPS, è possibile che tu sia stato in contatto o nelle vicinanze di una persona risultata positiva al COVID-19. Questo però non significa che tu sia infetto.\n\nPer maggiori informazioni o per sapere cosa fare, fai riferimento al sito della Mayo Clinic.", - "what_if_no_symptoms": "E se non mostro nessun sintomo?", + "what_if_no_symptoms": "Che cosa succede se non sto mostrando i sintomi?", "what_if_no_symptoms_para": "Se non hai alcun sintomo, ma vuoi comunque effettuare un test, puoi presentarti al centro più vicino.\n\nLe persone che non mostrano alcun sintomo possono, in alcune circostanze, essere ancora contagiosi. Mantieni alta l'attenzione e pratica il distanziamento sociale, evita di venire in contatto con gruppi di persone o con persone ad alto rischio (anziani o persone affette da altre patologie), è importante non solo per te, ma anche per gli altri." }, "import": { "button_text": "Importa lo storico delle posizioni", "google": { - "disclaimer": "Safe Paths non ha alcuna affiliazione a Google e non condivide mai i tuoi dati", + "disclaimer": "Safe Paths non ha alcuna affiliazione a Google e non condivide mai i tuoi dati.", + "instructions_detailed": "Visita Google Takeout ed esporta la tua Cronologia delle posizioni utilizzando le seguenti impostazioni: \n1. Metodo di consegna: \"Aggiungi a Drive\" \n2. Frequenza: \"Esporta una volta\" \n3. Tipo e dimensione del file: \".zip\" e \"1 GB\" \n4. Google ti invierà un'email quando l'esportazione è pronta \n5. Ritorna qui per importare le posizioni. Opzioni d'importazione: \n- Importa da Google Drive \n- Scarica dal browser, quindi importa il file dal tuo telefono. Assicurati di avere una connessione Wi-Fi attiva, poiché i file possono essere di grandi dimensioni.", + "instructions_first": "L'aggiunta dei dati di localizzazione di Google ti aiuterà a costruzione un primo storico delle tue posizioni recenti.", + "instructions_second": "Prima di poter importare i dati, devi effettuare il \"Take out\" dello storico delle tue geolocalizzazioni da Google.", "title": "Google Maps", "visit_button_text": "Visita Google Takeout" }, - "subtitle": "Per capire se hai incontrato qualcuno con COVID-19 prima di scaricare questa app, puoi importare lo storico delle tue posizioni", + "subtitle": "Per capire se hai incontrato una persona positiva al COVID-19 prima di scaricare questa applicazione, puoi importare lo storico delle tue posizioni.", "title": "Importa posizioni" }, "label": { @@ -25,19 +36,30 @@ "authorities_add_url": "Aggiungi istituzione sanitaria via URL", "authorities_desc": "Seleziona le istituzioni sanitarie nella tua area per ottenere i dati sulle possibilità di esposizione.\n\nSeleziona un nome dal registro globale o inserisci l'indirizzo internet fornito da una istituzione sanitaria che partecipa a Safe Paths.", "authorities_input_placeholder": "Incolla l'indirizzo qui", + "authorities_new_in_area_msg": "Iscritto ad {{count}} Istitituzione Sanitaria accreditata per la tua area.", + "authorities_new_in_area_msg_plural": "Iscritto a {{count}} Istitituzioni Sanitarie accreditate per la tua area.", + "authorities_new_in_area_title": "Nuova Istituzione Sanitaria", + "authorities_new_in_area_title_plural": "Nuove Istituzioni Sanitarie", + "authorities_new_subcription_msg": "Ti sei registrato ad {{count}} nuova Instituzione nella tua zona", + "authorities_new_subcription_msg_plural": "Ti sei registrato a {{count}} nuove Instituzioni nella tua zona", + "authorities_new_subcription_title": "{{numAuthories}} Nuova Iscrizione", + "authorities_new_subcription_title_plural": "{{numAuthories}} Nuove Iscrizioni", "authorities_no_sources": "Nessuna fonte di dati per ora", "authorities_removal_alert_cancel": "Annulla", "authorities_removal_alert_desc": "Sei sicuro di voler rimuovere questa fonte di dati sanitari?", "authorities_removal_alert_proceed": "Procedi", - "authorities_removal_alert_title": "Rimuovi istituzione sanitaria", + "authorities_removal_alert_title": "Rimuovi Istituzione Sanitaria", "authorities_title": "Istituzioni sanitarie", - "choose_provider_subtitle": "Per essere informato sulle possibilità di esposizione devi aggiungere almeno una istituzione sanitaria", - "choose_provider_title": "Seleziona le istituzioni sanitarie", + "auto_subscribe_checkbox": "Attiva sottoscrizione automatica", + "choose_provider_subtitle": "Per essere informato sulle possibilità di esposizione devi aggiungere almeno una Istituzione Sanitaria.", + "choose_provider_title": "Seleziona le Istituzioni Sanitarie", "commitment": "Missione", "commitment_para": "Safe Paths registra i dati in piena sicurezza e controlla l'interazione con altre persone usando la tua posizione.\nI tuoi dati non lasceranno MAI il tuo telefono senza il tuo consenso", "default_news_site_name": "Novità su Safe Paths", + "enter_authority_url": "Inserisci o incolla l'URL", "event_history_subtitle": "Controlla la tua possibile esposizione sulla base delle informazioni condivise dalle istituzioni sanitarie", "event_history_title": "Storico delle esposizioni", + "filter_authorities_by_gps_history": "Filtra in base alle tue località", "home_at_risk_header": "Potresti esser stato esposto", "home_at_risk_subsubtext": "Questo non significa che tu sia infetto", "home_at_risk_subtext": "Sulla base del tuo storico GPS, è possibile che tu sia venuto a contatto o sia nelle vicinanze di qualcuno con diagnosi di COVID-19", @@ -49,10 +71,14 @@ "home_setting_off_header": "Sconosciuto", "home_setting_off_subtext": "Non possiamo darti informazioni sull'esposizione al virus se non attivi la localizzazione.", "home_unknown_header": "Sconosciuto", - "home_unknown_subtext": "Non è possibile sapere se sei stato esposto se non permetti alla app di accedere alla tua posizione", + "home_unknown_subtext": "Non è possibile sapere se sei stato esposto se non permetti all'applicazione di accedere alla tua posizione.", "latest_news": "Ultime novità", + "launch_authority_access": "Iscriviti alle istituzioni sanitarie vicine", + "launch_authority_header": "Le istituzioni sanitarie forniranno al tuo dispositivo i dati per sapere se hai incrociato una persona infetta", + "launch_authority_subheader": "Iscriviti automaticamente per ricevere gli ultimi aggiornamenti dalle istituzioni sanitarie della tua zona.", "launch_done_header": "Fatto!", "launch_done_subheader": "Sei pronto. Ricorda, puoi sempre modificare le impostazioni più tardi.", + "launch_enable_auto_subscription": "Attiva sottoscrizione automatica", "launch_enable_location": "Attiva la Localizzazione", "launch_enable_notif": "Attiva le Notifiche", "launch_finish_set_up": "Finisci la Configurazione", @@ -67,7 +93,7 @@ "launch_screen1_header": "Il ritorno alla normalità comincia da qui.", "launch_screen2_header": "Ricevi notifiche se hai incrociato il percorso di chi è stato in seguito diagnosticato con COVID-19.", "launch_screen2_subheader": "Sapere è potere.", - "launch_screen3_header": "Se risulti positivo, puoi scegliere di donare i tuoi dati anonimamente.", + "launch_screen3_header": "Se risulti positivo, puoi scegliere di donare i tuoi dati anonimamente", "launch_screen3_subheader": "Il che aiuta l'intera comunità a proteggersi.", "launch_screen4_header": "Hai il pieno controllo.\nI dati sono salvati solo sul tuo dispositivo.", "launch_screen4_subheader": "Se risulti positivo, solo tu puoi scegliere se condividerli o meno.", @@ -76,19 +102,20 @@ "loading_public_data": "Caricando i dati…", "location_disabled_message": "COVID Safe Paths ha bisogno dei servizi di localizzazione", "location_disabled_title": "Il tracciamento della posizione è stato disabilitato", - "location_enabled_message": "COVID Safe Paths sta registrando in sicurezza le tue coordinate GPS ogni 5 minuti su questo dispositivo", + "location_enabled_message": "COVID Safe Paths sta registrando in sicurezza le tue coordinate GPS ogni 5 minuti su questo dispositivo.", "location_enabled_title": "COVID Safe Paths è abilitato", "logging_active": "Localizzazione attiva", "logging_inactive": "Localizzazione disattiva", - "news_subtitle": "Leggi gli aggiornamenti su COVID-19 da parte delle tue istituzioni sanitarie e nel mondo", + "news_subtitle": "Leggi gli aggiornamenti su COVID-19 da parte delle tue istituzioni sanitarie e nel mondo.", "news_title": "Ultime novità", "no_data": "Nessun dato", "push_at_risk_message": "Hai incrociato il percorso di un paziente COVID-19", "push_at_risk_title": "Potresti essere a rischio di esposizione", "see_exposure_history": "Visualizza lo storico delle esposizioni", - "settings_title": "Settaggi", + "settings_title": "Pannello di controllo", + "skip_this_step": "Salta questo passaggio", "team": "Team", - "team_para": "Il nostro team è composto da un consorzio di epidemiologi, ingegneri, data scientist, esperti di privacy digitale, professori e ricercatori dalle più importanti istituzioni, incluse: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young e Link Ventures", + "team_para": "Il nostro team è composto da un consorzio di epidemiologi, ingegneri, data scientist, esperti di privacy digitale, professori e ricercatori dalle più importanti istituzioni, incluse: MIT, Harvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young e Link Ventures.", "terms_of_use": "Termini per l'utilizzo" }, "onboarding": { @@ -98,14 +125,14 @@ }, "share": { "button_text": "Condividi i tuoi dati GPS", - "paragraph_first": "Se risulti COVID-19 positivo, per favore aiutaci condividendo lo storico delle tue posizioni con le autorità sanitarie", - "paragraph_second": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva", - "subtitle": "I tuoi dati personali possono essere trasferiti alle istituzioni sanitarie, salvati o condivisi in altra maniera", + "paragraph_first": "Se risulti positivo al COVID-19, per favore aiutaci condividendo lo storico delle tue posizioni con le istituzioni sanitarie.", + "paragraph_second": "La tua posizione è condivisa come una semplice lista di orari e luoghi, senza nessuna informazione aggiuntiva.", + "subtitle": "I tuoi dati personali possono essere trasferiti alle istituzioni sanitarie, salvati o condivisi in altra maniera.", "title": "Condividi lo storico delle posizioni" }, "version_update": { "alert_label": "COVID Safe Paths non è aggiornata", - "alert_sublabel": "Vuoi aggiornare ad una nuova versione?", + "alert_sublabel": "Vuoi aggiornare a una nuova versione?", "later": "Più tardi", "push_notification_message": "Per favore aggiorna l'applicazione", "push_notification_title": "L'applicazione non è aggiornata", diff --git a/app/locales/ml.json b/app/locales/ml.json index 1db5fdd549..78ed5e24d5 100644 --- a/app/locales/ml.json +++ b/app/locales/ml.json @@ -1,5 +1,13 @@ { "_display_name": "മലയാളം", + "about": { + "dimensions": "അളവുകൾ:", + "operating_system_abbr": "OS:", + "version": "പതിപ്പ്:" + }, + "common": { + "done": "ചെയ്‌തു" + }, "history": { "no_exposure": "സാധ്യതയില്ല", "possible_exposure": "സാധ്യത", @@ -13,6 +21,9 @@ "button_text": "പഴയ ലൊക്കേഷനുകൾ ഇംപോർട്ട് ചെയ്യുക", "google": { "disclaimer": "COVID സെയ്ഫ് പാത്ത്-ന് Google മായി ഒരു ബന്ധവുമില്ല കൂടാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും പങ്കിടില്ല.", + "instructions_detailed": "ഗൂഗിൾ ടേക്ഔട്ട് സന്ദർശിക്കുക, ഇനിപ്പറയുന്ന ക്രമീകരണങ്ങൾ ഉപയോഗിച്ചുകൊണ്ട് നിങ്ങളുടെ ലൊക്കേഷൻ ചരിത്രം എക്സ്പോർട്ട് ചെയ്യുക: \n1. ഡെലിവറി രീതി: \"ഡ്രൈവ് ചെയ്യാൻ ചേർക്കുക\" \n2. ആവൃത്തി: \"ഒരിക്കൽ എക്സ്പോർട്ട് ചെയ്യുക\" \n3. ഫയൽ തരം & വലിപ്പം: \". തപാൽ\", \"1GB\"\n4. കയറ്റുമതി തയ്യാറാകുമ്പോൾ ഗൂഗിൾ ഒരു ഇമെയിൽ അയയ്ക്കുന്നു \n5. ലൊക്കേഷനുകള് ഇറക്കുമതി ചെയ്യാന് ഇവിടെ തിരിച്ചെത്തും. ഇംപോര് ട്ട് ഉപാധികള്:\n-ഗൂഗിൾ ഡ്രൈവിൽ നിന്ന് ഇംപോർട്ട് ചെയ്യുക\n-ബ്രൗസറിൽ നിന്ന് ഡൗൺലോഡ് ചെയ്യുക, തുടർന്ന് പ്രാദേശിക ഫോൺ ഫയലുകളിൽ നിന്ന് ഇറക്കുമതി ചെയ്യുക. ഫയലുകൾ വലുതാക്കാം എന്നതിനാൽ വൈഫൈ നെറ്റ് വർക്കിലേക്ക് ഉറപ്പാക്കുക.", + "instructions_first": "Google- ൽ നിന്ന് ലൊക്കേഷൻ ഡാറ്റ ചേർക്കുന്നത് നിങ്ങളുടെ സമീപകാല ലൊക്കേഷനുകൾ നിർമ്മിക്കുന്നതിനുള്ള ഒരു തുടക്കം നൽകും.", + "instructions_second": "നിങ്ങൾക്ക് ഇംപോർട്ട് ചെയ്യാൻ കഴിയും മുമ്പ്, ഗൂഗിളിൽ നിന്നുള്ള നിങ്ങളുടെ ലൊക്കേഷൻ ഡാറ്റ ആദ്യം \"പുറത്തെടുക്കണം\".", "title": "Google ഭൂപടം", "visit_button_text": "ഗൂഗിൾ ടേക് ഔട്ട് സന്ദർശിക്കുക" }, @@ -25,19 +36,30 @@ "authorities_add_url": "URL വഴി ഉറവിടം ചേർക്കുക", "authorities_desc": "എക്‌സ്‌പോഷർ ഡാറ്റ നേടുന്നതിന് നിങ്ങളുടെ പ്രദേശത്തെ വിശ്വസനീയ ആരോഗ്യ സംരക്ഷണ അധികാരികളെ തിരഞ്ഞെടുക്കുക. ഒന്നുകിൽ ആഗോള രജിസ്ട്രിയിൽ നിന്ന് ഒരു പേര് തിരഞ്ഞെടുക്കുക, അല്ലെങ്കിൽ സെയ്ഫ് പാത്ത് നടപ്പിലാക്കിയ ഒരു അതോറിറ്റി നൽകിയ വെബ് വിലാസം നൽകുക.", "authorities_input_placeholder": "നിങ്ങളുടെ URL ഇവിടെ ചേർക്കുക", + "authorities_new_in_area_msg": "നിങ്ങളുടെ സ്ഥാനത്ത് {{count}} പുതിയ വിശ്വസനീയ ആരോഗ്യ സംരക്ഷണ അതോറിറ്റി സബ്‌സ്‌ക്രൈബുചെയ്യുക", + "authorities_new_in_area_msg_plural": "നിങ്ങളുടെ സ്ഥലത്തെ {{count}} പുതിയ വിശ്വസനീയ ആരോഗ്യ സംരക്ഷണ അതോറിറ്റികളിലേക്ക് സബ്‌സ്‌ക്രൈബുചെയ്യുക", + "authorities_new_in_area_title": "പുതിയ ആരോഗ്യ അതോറിറ്റി", + "authorities_new_in_area_title_plural": "പുതിയ ആരോഗ്യ സംരക്ഷണ അധികാരികൾ", + "authorities_new_subcription_msg": "\nനിങ്ങളുടെ സ്ഥാനത്തെ {{count}} പുതിയ അതോറിറ്റിയിലേക്ക് നിങ്ങൾ സബ്‌സ്‌ക്രൈബുചെയ്‌തു", + "authorities_new_subcription_msg_plural": "നിങ്ങളുടെ സ്ഥാനത്തെ {{count}} പുതിയ അധികാരികളിലേക്ക് നിങ്ങൾ സബ്‌സ്‌ക്രൈബുചെയ്‌തു", + "authorities_new_subcription_title": "{{count}} പുതിയ സബ്‌സ്‌ക്രിപ്‌ഷൻ", + "authorities_new_subcription_title_plural": "{{count}} പുതിയ സബ്‌സ്‌ക്രിപ്‌ഷനുകൾ", "authorities_no_sources": "ഇതുവരെ ഡാറ്റ ഉറവിടങ്ങളൊന്നുമില്ല", "authorities_removal_alert_cancel": "റദ്ദാക്കുക", "authorities_removal_alert_desc": "ഈ അതോറിറ്റി ഡാറ്റ ഉറവിടം നീക്കംചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", "authorities_removal_alert_proceed": "തുടരുക", - "authorities_removal_alert_title": "അതോറിറ്റി(ഉറവിടം) നീക്കംചെയ്യുക", + "authorities_removal_alert_title": "അധികാരനീക്കം", "authorities_title": "വിശ്വസനീയമായ ഉറവിടങ്ങൾ", + "auto_subscribe_checkbox": "ഓട്ടോ സബ് സ്ക്രിപ്ഷൻ പ്രാപ്തമാക്കുക", "choose_provider_subtitle": "എക്‌സ്‌പോഷറുകളെക്കുറിച്ച് അറിയാൻ നിങ്ങൾ ഒരു ഹെൽത്ത് അതോറിറ്റിയിലേക്ക് സബ്‌സ്‌ക്രൈബു ചെയ്യേണ്ടതുണ്ട്.", "choose_provider_title": "ആരോഗ്യ അതോറിറ്റി തിരഞ്ഞെടുക്കുക", "commitment": "പ്രതിബദ്ധത", "commitment_para": "നിങ്ങളുടെ സെയ്ഫ് പാത്ത് ഉപയോഗിക്കുന്ന ആളുകളുമായുള്ള നിങ്ങളുടെ ഇടപെടൽ സുരക്ഷിതമായി രേഖപ്പെടുത്തുകയും പരിശോധിക്കുകയും ചെയ്യുന്നു. നിങ്ങളുടെ സമ്മതമില്ലാതെ നിങ്ങളുടെ ഡാറ്റ ഒരിക്കലും ഫോൺ വിടുകയില്ല.", "default_news_site_name": "സെയ്ഫ് പാത്ത് വാർത്തകൾ", + "enter_authority_url": "URL നൽകുക അല്ലെങ്കിൽ പേസ്റ്റ് ചെയ്യുക", "event_history_subtitle": "ആരോഗ്യ അധികാരികൾ പങ്കിട്ട വിവരങ്ങളെ അടിസ്ഥാനമാക്കി നിങ്ങളുടെ എക്സ്പോഷർ മനസ്സിലാക്കുക.", "event_history_title": "എക്സ്പോഷർ ചരിത്രം", + "filter_authorities_by_gps_history": "നിങ്ങളുടെ ലൊക്കേഷനുകൾ പ്രകാരം ഫിൽട്ടർ ചെയ്യുക", "home_at_risk_header": "You May Be Exposed", "home_at_risk_subsubtext": "നിങ്ങൾ രോഗബാധിതനാണെന്ന് ഇതിനർത്ഥമില്ല", "home_at_risk_subtext": "നിങ്ങളുടെ ജി‌പി‌എസ് ചരിത്രത്തെ അടിസ്ഥാനമാക്കി, നിങ്ങൾ‌ COVID-19 രോഗനിർണയം നടത്തിയ ഒരാളുമായി സമ്പർക്കം പുലർത്തുകയോ അല്ലെങ്കിൽ‌ അവരുമായി അടുത്തിടപഴകുകയോ ചെയ്‌തിരിക്കാം.", @@ -51,8 +73,12 @@ "home_unknown_header": "അജ്ഞാതം", "home_unknown_subtext": "നിങ്ങളുടെ ലൊക്കേഷൻ ആക്‌സസ്സുചെയ്യാൻ അപ്ലിക്കേഷൻ പ്രാപ്‌തമാക്കിയില്ലെങ്കിൽ നിങ്ങൾക്ക് അപകടസാധ്യതയുണ്ടോ ഞങ്ങൾക്ക് പറയാനാവില്ല.", "latest_news": "പുതിയ വാർത്ത", + "launch_authority_access": "അടുത്തുള്ള ആരോഗ്യ അതോറിറ്റികൾക്ക് സബ് സ്ക്രൈബ് ചെയ്യുക", + "launch_authority_header": "രോഗബാധിതനായ ഒരാളുമായി നിങ്ങൾ പാത മുറിച്ചുകടന്നിട്ടുണ്ടോ എന്നറിയാൻ ഹെൽത്ത്കെയർ അതോറിറ്റികൾ നിങ്ങളുടെ ഉപകരണത്തിന് പ്രാദേശിക ഡാറ്റ നൽകും", + "launch_authority_subheader": "നിങ്ങളുടെ പ്രദേശത്തെ ആരോഗ്യപരിപാലന അധികാരികളിൽ നിന്ന് ഏറ്റവും പുതിയ അപ് ഡേറ്റുകൾ സ്വീകരിക്കുന്നതിന് സ്വയമേവ സബ് സ്ക്രൈബ് ചെയ്യുക.", "launch_done_header": "എല്ലാം പൂർത്തിയായി", "launch_done_subheader": "ആരംഭിക്കാൻ നിങ്ങൾ തയ്യാറാണ്. ഓർമ്മിക്കുക, നിങ്ങൾക്ക് എല്ലായ്പ്പോഴും നിങ്ങളുടെ മുൻ‌ഗണനകൾ പിന്നീട് അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയും.", + "launch_enable_auto_subscription": "ഓട്ടോ സബ് സ്ക്രിപ്ഷൻ പ്രാപ്തമാക്കുക", "launch_enable_location": "ലൊക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കുക", "launch_enable_notif": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", "launch_finish_set_up": "സജ്ജീകരണം പൂർത്തിയാക്കുക", @@ -87,6 +113,7 @@ "push_at_risk_title": "നിങ്ങൾക്ക് അപകടസാധ്യതയുണ്ട്", "see_exposure_history": "എക്‌സ്‌പോഷർ ചരിത്രം കാണുക", "settings_title": "ഡാഷ്ബോർഡ്", + "skip_this_step": "ഈ ഘട്ടം ഒഴിവാക്കുക", "team": "ടീം", "team_para": "എപ്പിഡെമിയോളജിസ്റ്റുകൾ, എഞ്ചിനീയർമാർ, ഡാറ്റാ ശാസ്ത്രജ്ഞർ, ഡിജിറ്റൽ സ്വകാര്യതാ സുവിശേഷകർ, പ്രൊഫസർമാർ, പ്രശസ്ത സ്ഥാപനങ്ങളിൽ നിന്നുള്ള ഗവേഷകർ എന്നിവരുടെ ഒരു കൺസോർഷ്യം ഉൾപ്പെടുന്നതാണ് ഞങ്ങളുടെ ടീം: എംഐടി, ഹാർവാർഡ്, മയോ ക്ലിനിക്, ട്രിപ്പിൾബ്ലൈൻഡ്, ഐനെട്ര, ഏണസ്റ്റ് & യംഗ്, ലിങ്ക് സംരംഭം.", "terms_of_use": "നിബന്ധനകൾ" diff --git a/app/locales/pl.json b/app/locales/pl.json index 7d66b48ec9..1dbf2a104e 100644 --- a/app/locales/pl.json +++ b/app/locales/pl.json @@ -1,44 +1,84 @@ { "_display_name": "Polski", + "about": { + "dimensions": "Wymiary:", + "operating_system_abbr": "OS:", + "version": "Wersja:" + }, + "common": { + "done": "Gotowe" + }, + "history": { + "no_exposure": "Brak narażenia", + "possible_exposure": "Możliwość narażenia", + "possible_exposure_para": "Możliwe, że miałeś kontakt lub mogłeś być blisko osoby, która uzyskała pozytywny wynik testu na obecność COVID-19", + "what_does_this_mean": "Co to oznacza?", + "what_does_this_mean_para": "Na podstawie Twojej historii GPS możliwe jest, że miałeś kontakt lub mogłeś być blisko osoby, u której zdiagnozowano COVID-19. Nie oznacza to, że jesteś zakażony, ale możesz być.\n\nAby uzyskać więcej informacji na temat tego, co powinieneś zrobić, odwiedź stronę internetową Mayo Clinic.", + "what_if_no_symptoms": "Co, jeśli nie wykazuję objawów?", + "what_if_no_symptoms_para": "Jeśli nie masz objawów, ale nadal chcesz zrobić test, możesz udać się do najbliższego miejsca testowania.\n\nOsoby, które nie wykazują objawów, mogą czasem przenosić infekcję i zakażać innych. Bycie uważnym na dystans społeczny i kontakt z dużymi grupami lub osobami z grup ryzyka (osoby starsze, osoby z istotnymi innymi problemami medycznymi) jest ważne, aby zarządzać zarówno własnym ryzykiem, jak i ryzykiem w stosunku do innych." + }, + "import": { + "button_text": "Importuj poprzednie lokalizacje", + "google": { + "disclaimer": "Safe Paths nie jest powiązane z Google i nigdy nie udostępnia Twoich danych.", + "instructions_detailed": "Odwiedź Google Takeout i wyeksportuj Historię Lokalizacji przy użyciu następujących ustawień: \n 1. Sposób dostawy: „Dodaj do Dysku” \n 2. Częstotliwość: „Eksportuj raz” \n 3. Typ i rozmiar pliku: „.zip” i „1 GB” \n 4. Google wysyła wiadomość email, gdy eksport jest gotowy \n 5. Wróć tutaj, aby zaimportować lokalizacje. Opcje importu: \n - Importuj z Dysku Google \n - Pobierz z przeglądarki, a następnie zaimportuj z lokalnych plików telefonu. Upewnij się, że jesteś w sieci WiFi, ponieważ pliki mogą być duże.", + "instructions_first": "Dodanie danych lokalizacyjnych z Google zapewni Ci szybki start w tworzeniu Twoich ostatnich lokalizacji", + "instructions_second": "Przed przystąpieniem do importu należy najpierw „wyjąć” dane dotyczące lokalizacji z Google.", + "title": "Google Maps", + "visit_button_text": "Odwiedź Google Takeout" + }, + "subtitle": "Aby sprawdzić, czy spotkałeś kogoś z COVID-19 przed pobraniem tej aplikacji, możesz zaimportować swoją osobistą historię lokalizacji.", + "title": "Importowanie lokalizacji" + }, "label": { - "about_title": "O", + "about_title": "O nas", "authorities_add_button_label": "Dodaj zaufane źródło", "authorities_add_url": "Dodaj instytucję poprzez adres URL", - "authorities_desc": "Wybierz zaufaną instytucję opieki zdrowotnej w Twojej okolicy aby uzyskać dane dotyczące ryzyka narażenia na zakażenie. Wybierz nazwę z rejestru globalnego lub wprowadź adres internetowy podany przez organ który wdrożył Safe Paths.", + "authorities_desc": "Wybierz zaufaną instytucję opieki zdrowotnej w Twojej okolicy, aby uzyskać dane dotyczące ryzyka narażenia na zakażenie. Wybierz nazwę z rejestru globalnego lub wprowadź adres internetowy podany przez organ, który wdrożył Safe Paths.", "authorities_input_placeholder": "Wklej swój adres URL", + "authorities_new_in_area_msg_0": "Subskrybuj {{count}} nową zaufaną instytucję opieki zdrowotnej w swojej lokalizacji", + "authorities_new_in_area_msg_3": "Subskrybuj {{count}} nowe zaufane instytucje opieki zdrowotnej w swojej lokalizacji", + "authorities_new_subcription_msg_0": "Subskrybujesz {{count}} nową instytucję opieki zdrowotnej w swojej loklizacji", + "authorities_new_subcription_msg_3": "Subskrybujesz {{count}} nowe instytucje opieki zdrowotnej w swojej loklizacji", + "authorities_new_in_area_title_0": "Nowa instytucja opieki zdrowotnej", + "authorities_new_subcription_title_0": "{{count}} Nowa Subskrypcja", + "authorities_new_subcription_title_3": "{{count}} Nowe Subskrybcje", + "authorities_new_in_area_title_3": "Nowe instytucje opieki zdrowotnej", "authorities_no_sources": "Brak danych dotyczących instytucji", "authorities_removal_alert_cancel": "Anuluj", - "authorities_removal_alert_desc": "Czy jesteś pewien że chcesz usunąć dane dotyczące instytucji?", + "authorities_removal_alert_desc": "Czy jesteś pewien, że chcesz usunąć dane dotyczące instytucji?", "authorities_removal_alert_proceed": "Ok", "authorities_removal_alert_title": "Usuń instytucję", "authorities_title": "Zaufane źródło", - "choose_provider_subtitle": "Aby zostać poinformowanym o ryzyku narażenia się na zakażenie musisz subskrybować instytucję opieki zdrowotnej.", + "auto_subscribe_checkbox": "Włącz automatyczną subskrypcję", + "choose_provider_subtitle": "Aby zostać poinformowanym o ryzyku narażenia na zakażenie, musisz subskrybować instytucję opieki zdrowotnej.", "choose_provider_title": "Wybierz instytucję opieki zdrowotnej", "commitment": "Zobowiązanie", "commitment_para": "Safe Paths bezpiecznie rejestruje i sprawdza interakcje z osobami korzystającymi z Twojej lokalizacji. Twoje dane NIGDY nie opuszczą telefonu bez Twojej zgody.", - "default_news_site_name": "Informacje o Safe Paths", + "default_news_site_name": "Safe Paths Informacje", + "enter_authority_url": "Wprowadź lub wklej adres URL", "event_history_subtitle": "Dowiedz się o swoim osobistym narażeniu na ryzyko zakażenia na podstawie informacji udostępnionych przez instytucje opieki zdrowotnej.", - "event_history_title": "Historia narażenia się na zakażenie", - "export_para_1": "Jeśli wynik testu na obecność COVID-19 jest pozytywny, prosimy udostępnij swoją historię lokalizacji wszystkim lokalnym władzom.", - "export_para_2": "Lokalizacja jest udostępniana jako prosta lista składająca się z miejsc oraz czasu bez żadnych dodatkowych informacji.", + "event_history_title": "Historia narażenia na zakażenie", + "filter_authorities_by_gps_history": "Filtruj według lokalizacji", "home_at_risk_header": "Możesz być narażony", - "home_at_risk_subsubtext": "To nie oznacza że jesteś zakażony.", - "home_at_risk_subtext": "Na podstawie historii GPS możliwe jest, że byłeś w kontakcie lub w pobliżu osoby, u której zdiagnozowano COVID-19.", + "home_at_risk_subsubtext": "To nie oznacza, że jesteś zakażony.", + "home_at_risk_subtext": "Na podstawie Twojej historii GPS jest możliwe, że byłeś w kontakcie lub w pobliżu osoby, u której zdiagnozowano COVID-19.", "home_enable_location": "Zezwól na lokalizację danych", "home_mayo_link_heading": "Więcej informacji o COVID-19", "home_mayo_link_label": "z Mayo Clinic", - "home_no_contact_header": "Nieznany kontakt", + "home_no_contact_header": "Brak kontaktu", "home_no_contact_subtext": "W oparciu o dostępne dane nie znalazłeś się w pobliżu nikogo, kto miałby pozytywny wynik testu na obecność COVID-19.", + "home_setting_off_header": "Nieznany", + "home_setting_off_subtext": "Nie możemy stwierdzić, czy jesteś zagrożony, dopóki nie włączysz historii lokalizacji na ekranie ustawień.", "home_unknown_header": "Nieznany", "home_unknown_subtext": "Nie możemy stwierdzić, czy jesteś zagrożony, dopóki nie umożliwiasz aplikacji dostępu do Twojej lokalizacji.", - "home_setting_off_header": "Nieznana", - "home_setting_off_subtext": "Nie możemy stwierdzić, czy jesteś zagrożony, chyba że włączysz historię lokalizacji na ekranie ustawień.", - "import_step_1": "1. Zaloguj się do swojego konta Google i pobierz historię lokalizacji", - "import_step_2": "2. Po pobraniu historii lokalizacji otwórz ponownie ten ekran. Dane zostaną zaimportowane automatycznie.", - "import_title": "Importuj lokalizację", "latest_news": "Najnowsze informacje", + "launch_authority_access": "Subskrybuj pobliskie instytucje opieki zdrowotnej", + "launch_authority_header": "Instytucje opieki zdrowotnej przekażą na Twoje urządzenie dane lokalne, aby dowiedzieć się, czy spotkałeś na swojej drodze osobę zakażoną.", + "launch_authority_subheader": "Automatyczna subskrypcja, aby otrzymywać najnowsze aktualizacje od instytucji opieki zdrowotnej w Twojej okolicy.", "launch_done_header": "Wszystko ukończone", "launch_done_subheader": "Możesz zaczynać. Pamiętaj, że zawsze możesz później zaktualizować swoje preferencje.", + "launch_enable_auto_subscription": "Włącz automatyczną subskrypcję", "launch_enable_location": "Włącz lokalizację", "launch_enable_notif": "Włącz powiadomienia", "launch_finish_set_up": "Zakończ konfigurację", @@ -59,56 +99,43 @@ "launch_screen4_subheader": "Jeśli wynik Twojego testu jest pozytywny, sam możesz wybrać, czy chcesz to udostępnić.", "launch_set_up_phone": "Skonfiguruj mój telefon", "legal_page_title": "Informacje prawne", - "less_than_one_minute": "mniej niż jedna minuta", "loading_public_data": "ładowanie danych...", - "location_disabled_message": "COVID Safe Paths wymaga dostępu do usług lokalizacyji.", + "location_disabled_message": "COVID Safe Paths wymaga dostępu do usług lokalizacyjnych.", "location_disabled_title": "Śledzenie lokalizacji zostało wyłączone", - "location_enabled_message": "COVID Safe Paths bezpiecznie przechowuje współrzędne GPS raz na pięć minut na tym urządzeniu.", + "location_enabled_message": "COVID Safe Paths bezpiecznie przechowuje współrzędne GPS na tym urządzeniu raz na pięć minut.", "location_enabled_title": "Włączono COVID Safe Paths", - "maps_import_button_text": "Importuj poprzednie lokalizacje", - "maps_import_disclaimer": "Safe Paths nie jest powiązane z Google i nigdy nie udostępnia Twoich danych.", - "maps_import_text": "Aby sprawdzić, czy spotkałeś kogoś z COVID-19 przed pobraniem tej aplikacji, możesz zaimportować osobistą historię lokalizacji.", - "maps_import_title": "Google Maps", - "nCoV2019_url_info": "Dowiedz się więcej o zestawie danych dla tej mapy", - "news_subtitle": "Przeczytaj o najnowszych aktualizacjach COVID z Twojej instytucji zdrowia i ogólnie.", + "logging_active": "Lokalizacja Aktywna", + "logging_inactive": "Lokalizacja Nieaktywna", + "news_subtitle": "Przeczytaj o najnowszych aktualizacjach COVID z Twojej instytucji zdrowia i inne.", "news_title": "Najnowsze informacje", "no_data": "Brak danych", - "notification_2_weeks_ago": "2 tygodnie temu", - "notification_data_not_available": "Brak dostępnych danych na temat narażenia.", - "notification_select_authority": "Wybierz instytucję opieki zdrowotnej", - "notification_title": "Profil narażenia na zakażenie w ciągu ostatnich dwóch tygodni", - "notification_today": "dziś", - "notification_warning_text": "Jeśli w Twojej okolicy istnieje instytucja opieki zdrowotnej, możesz zapisać się, aby otrzymywać regularne aktualizacje dotyczące ryzyka narażenia na zakażenie.", - "notifications_exposure_format": "{{daysAgo}} dni temu spotkałeś na swojej drodze osobę zakażoną przez {{exposureTime}} minut.", - "notifications_exposure_format_today": "Dziś spotkałeś na swojej drodze osobę zakażoną przez {{exposureTime}} minut.", - "notifications_exposure_format_yesterday": "Wczoraj spotkałeś na swojej drodze osobę zakażoną przez {{exposureTime}} minut.", - "notifications_no_exposure": "Brak narażenia na zakażenie COVID-19 w ciągu ostatnich dwóch tygodni.", - "overlap_found_button_label": "Załadowano dane publiczne", - "overlap_no_results_button_label": "Załadowano dane publiczne", - "overlap_para_1": "Zielony ślad reprezentuje historię lokalizacji\n\nJasnofioletowe koła reprezentują zestaw danych publicznych", - "overlap_title": "Sprawdź zakładkę", "push_at_risk_message": "Spotkałeś na swojej drodze osobę zakażoną COVID-19", "push_at_risk_title": "Możesz być w niebezpieczeństwie", "see_exposure_history": "Zobacz historię narażenia", "settings_title": "Tablica", - "share_location_data": "Udostępnij dane o lokalizacji", - "show_overlap": "Kliknij aby wyświetlić zestaw danych publicznych", + "skip_this_step": "Pomiń ten krok", "team": "Zespół", "team_para": "Nasz zespół składa się z konsorcjum epidemiologów, inżynierów, naukowców, cyfrowych ewangelistów prywatności, profesorów i badaczy z uznanych instytucji, w tym: MIT, Hardvard, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young oraz Link Ventures.", - "terms_of_use": "Warunki korzystania", - "tested_positive_subtitle": "Twoje dane prywatne mogą być przekazywane do instytucji zdrowia, archiwizowane lub w inny sposób udostępniane.", - "tested_positive_title": "Udostępnij historię lokalizacji", - "logging_active": "Lokalizacja Aktywna", - "logging_inactive": "Lokalizacja Nieaktywna" + "terms_of_use": "Warunki korzystania" }, - "history": { - "no_exposure": "Brak narażenia", - "possible_exposure_para": "Możliwe, że miałeś kontakt lub mogłeś być blisko osoby, która uzyskała pozytywny wynik testu na obecność COVID-19", - "possible_exposure": "Możliwość narażenia", - "timeline": "Oś czasu", - "what_does_this_mean_para": "Na podstawie Twojej historii GPS możliwe jest, że miałeś kontakt lub mogłeś być blisko osoby, u której zdiagnozowano COVID19. Nie oznacza to, że jesteś zakażony, ale możesz być.\n\nAby uzyskać więcej informacji na temat tego, co powinieneś zrobić, odwiedź stronę internetową Mayo Clinic.", - "what_does_this_mean": "Co to oznacza?", - "what_if_no_symptoms_para": "Jeśli nie masz objawów, ale nadal chcesz zrobić test, możesz udać się do najbliższego miejsca testowania.\n\nOsoby, które nie wykazują objawów, mogą czasem przenosić infekcję i infekować innych. Bycie uważnym na dystans społeczny i kontakt z dużymi grupami lub osobami z grup ryzyka (osoby starsze, osoby z istotnymi innymi problemami medycznymi) jest ważne, aby zarządzać zarówno własnym ryzykiem, jak i ryzykiem w stosunku do innych.", - "what_if_no_symptoms": "Co jeśli nie wykazuję objawów?" + "onboarding": { + "eula_checkbox": "Akceptuję umowę licencyjną", + "eula_continue": "Dalej", + "eula_message": "Musisz zaakceptować, jeśli chcesz korzystać z Safe Paths" + }, + "share": { + "button_text": "Udostępnij dane o lokalizacji", + "paragraph_first": "Jeśli wynik Twojego testu na COVID-19 jest pozytywny, prosimy o udostępnienie swojej historii lokalizacji lokalnym władzom.", + "paragraph_second": "Lokalizacja jest udostępniana jako prosta lista czasów i miejsc, bez dodatkowych informacji.", + "subtitle": "Twoje prywatne dane mogą być przekazywane organom zdrowia, archiwizowane lub w inny sposób udostępniane.", + "title": "Udostępnij historię lokalizacji" + }, + "version_update": { + "alert_label": "Aplikacja COVID Safe Paths jest nieaktualna", + "alert_sublabel": "Zaktualizować do nowej wersji?", + "later": "Później", + "push_notification_message": "Proszę zaktualizować swoją aplikację", + "push_notification_title": "Aplikacja jest nieaktualna", + "update": "Aktualizacja" } } diff --git a/app/locales/pull.sh b/app/locales/pull.sh index e7ca82180d..c640fc8673 100755 --- a/app/locales/pull.sh +++ b/app/locales/pull.sh @@ -28,10 +28,11 @@ fi echo "Downloading iOS *.strings" lokalise2 file download \ --add-newline-eof \ - --export-empty-as skip \ + --export-empty-as=skip \ --format strings \ --include-description \ --original-filenames \ + --placeholder-format=ios \ --unzip-to=ios \ --export-sort=a_z \ --config .lokalise.yml --token=$LOKALISE_TOKEN diff --git a/app/locales/ru.json b/app/locales/ru.json index caa0b18126..ad7c5cbc09 100644 --- a/app/locales/ru.json +++ b/app/locales/ru.json @@ -1,5 +1,13 @@ { "_display_name": "Русский", + "about": { + "dimensions": "Размеры:", + "operating_system_abbr": "Операционная система:", + "version": "Версия:" + }, + "common": { + "done": "Готово" + }, "history": { "no_exposure": "Неизвестно", "possible_exposure": "Возможно", @@ -13,6 +21,9 @@ "button_text": "Импортировать последние местонахождения", "google": { "disclaimer": "Safe Paths не является партнером Google и не распространяет ваши данные.", + "instructions_detailed": "Зайдите в Google Takeout и экспортируйте историю местонахождений, используя следующие настройки: \n1. Способ получения: \"Добавить на Диск\" \n2. Тип экспорта: \"Однократный экспорт\" \n3. Формат и размер файла: \".zip\" и \"1GB\"\n4. Когда экспорт будет завершён, Гугл пришлет вам письмо по электронной почте \n5. Вернитесь сюда и импортируйте местонахождения. Параметры импорта:\n- Импортировать с Google Диска\n- Скачать через браузер, затем импортировать из файлов на телефоне. Убедитесь, что у вы в сети WiFi, так как файлы могут оказаться большими.", + "instructions_first": "Добавив данные геопозиции из Google, вы сможете проще начать отслеживать свои недавние местоположения.", + "instructions_second": "Прежде, чем начать импорт, вам необходимо 'забрать' данные о вашем местоположении из Google.", "title": "Google Карты", "visit_button_text": "Перейти на Google Takeout" }, @@ -25,19 +36,30 @@ "authorities_add_url": "Добавить организацию по ссылке", "authorities_desc": "Выберите надёжные органы здравоохранения в вашем регионе, чтобы получать данные о зонах риска. Выберите название организации из международного реестра или введите ссылку организации, которая использует Safe Paths.", "authorities_input_placeholder": "Вставьте ссылку сюда", + "authorities_new_in_area_msg_0": "Подписаться на {{count}} новый надежный орган здравоохранения в вашем регионе", + "authorities_new_in_area_msg_3": "Подписаться на {{count}} новых надежных органов здравоохранения в вашем регионе", + "authorities_new_subcription_msg_0": "Вы подписались на {{count}} новую местную организацию", + "authorities_new_subcription_msg_3": "Вы подписались на {{count}} новых местных организаций", + "authorities_new_in_area_title_0": "Новая организация здравоохранения", + "authorities_new_subcription_title_0": "Новая подписка", + "authorities_new_subcription_title_3": "Новые подписки", + "authorities_new_in_area_title_3": "Новые организации здравоохранения", "authorities_no_sources": "Нет источника данных", "authorities_removal_alert_cancel": "Отмена", "authorities_removal_alert_desc": "Вы уверены, что хотите удалить этот источник данных?", "authorities_removal_alert_proceed": "Продолжить", "authorities_removal_alert_title": "Удалить организацию", "authorities_title": "Надёжные источники", + "auto_subscribe_checkbox": "Включить автоматическую подписку", "choose_provider_subtitle": "Чтобы получать информацию о возможных рисках, вам нужно подписаться на орган здравоохранения.", "choose_provider_title": "Выбрать орган здравоохранения", "commitment": "Гарантии", "commitment_para": "Safe Paths ведет безопасную запись и отслеживание вашего взаимодействия с другими людьми, используя вашу геопозицию. При этом данные о вашем местонахождении НИКОГДА не передаются с вашего устройства без вашего согласия.", "default_news_site_name": "Новости Safe Paths", + "enter_authority_url": "Введите или вставьте URL", "event_history_subtitle": "Оцените собственные риски на основе информации от органов здравоохранения.", "event_history_title": "История возможных рисков", + "filter_authorities_by_gps_history": "Отсортировать по местоположению", "home_at_risk_header": "Возможно, вы в опасной зоне", "home_at_risk_subsubtext": "Это не значит, что вы заразились.", "home_at_risk_subtext": "Судя по вашей GPS истории, есть вероятность, что вы контактировали или находились рядом с человеком, инфицированным COVID-19.", @@ -51,8 +73,12 @@ "home_unknown_header": "Неизвестно", "home_unknown_subtext": "Мы не сможем сказать, подвергались ли вы риску, пока вы не разрешите приложению доступ к вашей геопозиции", "latest_news": "Последние новости", + "launch_authority_access": "Подписаться на близлежащие организации здравоохранения", + "launch_authority_header": "Организации здравоохранения пришлют местные данные на ваше устройство, если вы окажетесь рядом с инфицированным человеком", + "launch_authority_subheader": "Подпишитесь на автоматическую рассылку, чтобы получать последние новости от организаций здравоохранения в вашем регионе.", "launch_done_header": "Готово", "launch_done_subheader": "Можно начинать. Помните, что вы в любой момент можете изменить настройки.", + "launch_enable_auto_subscription": "Включить автоматическую подписку", "launch_enable_location": "Включить отслеживание местоположения", "launch_enable_notif": "Включить уведомления", "launch_finish_set_up": "Завершить настройку", @@ -87,6 +113,7 @@ "push_at_risk_title": "Возможно, вы в зоне риска", "see_exposure_history": "Смотреть историю возможных рисков", "settings_title": "Контрольная панель", + "skip_this_step": "Пропустить этот шаг", "team": "Команда", "team_para": "Наша команда состоит из эпидемиологов, инженеров, специалистов по обработке данных, евангелистов цифровой безопасности, профессоров и исследователей институтов с мировым именем, таких как МТИ (MIT), Гарвард, Клиника Мэйо, TripleBlind, EyeNetra, Ernst & Young и Link Ventures.", "terms_of_use": "Условия использования" diff --git a/app/locales/sk.json b/app/locales/sk.json index 6e59959096..296ad0d15a 100644 --- a/app/locales/sk.json +++ b/app/locales/sk.json @@ -1,5 +1,13 @@ { - "_display_name": "Slovak", + "_display_name": "Slovenčina", + "about": { + "dimensions": "Rozmery:", + "operating_system_abbr": "OS:", + "version": "Verzia:" + }, + "common": { + "done": "Hotovo" + }, "history": { "no_exposure": "Žiadne vystavenie sa riziku", "possible_exposure": "Možné vystavenie sa riziku", @@ -13,9 +21,13 @@ "button_text": "Importovať históriu polôh", "google": { "disclaimer": "Safe Paths nemá žiadne prepojenie s Google a nikdy nezdieľa tvoje údaje.", - "title": "Google Maps" + "instructions_detailed": "Navštív Google Takeout a exportuj históriu polôh pomocou nasledujúcich nastavení: \n1. Spôsob doručenia: \"Pridať do Google Drive\"\n2. Frekvencia: \"Exportovať raz\"\n3. Typ a veľkosť súboru: \".zip\" a \"1GB\"\n4. Google ti pošle email, keď je export dát hotový\n5. Vráť sa sem a importuj sem svoje polohy. Možnosti importovania:\n- Importovať z Google Drive\n- Stiahnuť z prehliadača a následne importovať zo súborov tvojho telefónu. Uisti sa, že si prihlásený na WiFi, keďže súbory môžu byť objemné. ", + "instructions_first": "Pridaním údajov o polohe od spoločnosti Google získaš náskok pri vytváraní tvojich nedávnych polôh.", + "instructions_second": "Skôr než budeš môcť importovať svoje dáta, musíš najskôr \"vybrať\" údaje o svojej polohe od spoločnosti Google.", + "title": "Google Maps", + "visit_button_text": "Navštív Google Takeout" }, - "subtitle": "Ak chceš zistiť, či si sa stretol/la s niekým, kto má COVID-19 pred stiahnutím tejto aplikácie, môžeš importovať tvoju osobnú históriu geografických polôh.", + "subtitle": "Ak chceš zistiť pred stiahnutím tejto aplikácie, či si sa stretol/la s niekým, komu bol diagnostikovaný COVID-19, môžeš importovať svoju históriu geografických polôh.", "title": "Importovať polohy" }, "label": { @@ -24,19 +36,30 @@ "authorities_add_url": "Pridať úrad prostredníctvom URL", "authorities_desc": "Ak chceš získať údaje o tvojom vystavení sa riziku, vyber dôveryhodné zdravotné úrady v tvojej oblasti. Vyber meno z globálneho registra alebo zadaj webovú adresu poskytnutú orgánom, ktorú implementoval Safe Paths.", "authorities_input_placeholder": "Sem vlož tvoje URL", + "authorities_new_in_area_msg_0": "Prihlásiť sa na odber informácií od nového dôveryhodného zdravotného úradu v tvojej lokalite", + "authorities_new_in_area_msg_3": "Prihlásiť sa na odber informácií od nových dôveryhodných zdravotných úradov v tvojej lokalite", + "authorities_new_subcription_msg_0": "Prihlásil/a si sa na odber informácií od nového zdravotného úradu v tvojej lokalite", + "authorities_new_subcription_msg_3": "Prihlásil/a si sa na odber informácií od nových zdravotných úradov v tvojej lokalite", + "authorities_new_in_area_title_0": "Nový zdravotný úrad", + "authorities_new_subcription_title_0": "Nový odber", + "authorities_new_subcription_title_3": "Nové odbery", + "authorities_new_in_area_title_3": "Nové zdravotné úrady", "authorities_no_sources": "Zatiaľ žiadny zdroj údajov", "authorities_removal_alert_cancel": "Ukončiť", "authorities_removal_alert_desc": "Si si istý/á, že chceš odstrániť zdroj údajov tohto úradu?", "authorities_removal_alert_proceed": "Pokračovať", "authorities_removal_alert_title": "Odstrániť úrad", "authorities_title": "Dôveryhodné zdroje", + "auto_subscribe_checkbox": "Umožniť automatický odber", "choose_provider_subtitle": "Na to, aby si bol/a informovaný/á o tvojom vystavení sa riziku, je potrebné prihlásiť sa na odber od zdravotného úradu.", "choose_provider_title": "Vybrať zdravotný úrad", "commitment": "Záväzok", "commitment_para": "Safe Paths bezpečne zaznamenáva a kontroluje tvoj prienik s ľuďmi, ktorí používajú tvoju polohu. Tvoje dáta NIKDY neopustia tvoj telefón bez tvojho súhlasu.", "default_news_site_name": "Správy o Safe Paths", + "enter_authority_url": "Zadaj alebo skopíruj URL adresu ", "event_history_subtitle": "Zisti o svojom osobnom vystavení sa riziku na základe informácií zdieľaných zdravotnými úradmi.", "event_history_title": "História vystavenia riziku", + "filter_authorities_by_gps_history": "Filtruj na základe tvojich geografických polôh", "home_at_risk_header": "Môžeš byť vystavený/á riziku", "home_at_risk_subsubtext": "Toto znamená, že nie si nakazený/á", "home_at_risk_subtext": "Na základe histórie tvojho GPS, je možné, že si bol/a v kontakte alebo blízko niekoho, komu bol diagnostikovaný COVID-19", @@ -45,11 +68,17 @@ "home_mayo_link_label": "z Mayo Clinic", "home_no_contact_header": "Žiadny známy kontakt", "home_no_contact_subtext": "Na základe dostupných dát si nebol/a v blízkosti nikoho, kto je nahlásený ako pozitívne testovaný na COVID-19.", + "home_setting_off_header": "Neznámy", + "home_setting_off_subtext": "Kým nepovolíš aplikácii prístup k tvojej polohe, nemôžeme ti povedať, či si v ohrození. ", "home_unknown_header": "Neznámy", "home_unknown_subtext": "Kým nepovolíš aplikácii prístup k tvojej polohe, nemôžeme ti povedať, či si v ohrození.", "latest_news": "Najnovšie oznámenia", + "launch_authority_access": "Prihlásiť na odber informácií od okolitých zdravotných úradov", + "launch_authority_header": "Zdravotné úrady ti prostredníctvom tvojho zariadenia poskytnú miestne údaje, či si sa stretol/la s infikovanou osobou. ", + "launch_authority_subheader": "Automaticky sa prihlásiť na odber najnovších aktualizácií od okolitých zdravotných úradov.", "launch_done_header": "Všetko je ukončené", "launch_done_subheader": "Môžeš začať. Nezabudni, že svoje preferencie môžeš kedykoľvek aktualizovať.", + "launch_enable_auto_subscription": "Umožniť automatický odber", "launch_enable_location": "Umožniť lokalizáciu", "launch_enable_notif": "Umožniť upozornenia", "launch_finish_set_up": "Dokončiť nastavenia", @@ -69,12 +98,14 @@ "launch_screen4_header": "Máš všetko pod úplnou kontrolou. Údaje sú uložené iba na tvojom telefóne.", "launch_screen4_subheader": "Ak si testovaný/á pozitívne, môžeš sa sám/a rozhodnúť, či budeš zdieľať svoje dáta.", "launch_set_up_phone": "Nastaviť môj telefón", - "legal_page_title": "Legálny", + "legal_page_title": "Právne oznámenie\n", "loading_public_data": "Dáta sa načítavajú", "location_disabled_message": "COVID Safe Paths vyžaduje lokalizačné služby.", "location_disabled_title": "Sledovanie polohy bolo deaktivované.", "location_enabled_message": "COVID Safe Paths bezpečne ukladá vaše GPS súradnice každých päť minút.", "location_enabled_title": "COVID Safe Paths bol povolený", + "logging_active": "Aktívna poloha", + "logging_inactive": "Neaktívna poloha", "news_subtitle": "Prečítaj si informácie o najnovších aktualizáciách týkajúcich sa COVID-19 od vášho zdravotného úradu a vo všeobecnosti.", "news_title": "Najnovšie oznámenia", "no_data": "Žiadne údaje", @@ -82,13 +113,29 @@ "push_at_risk_title": "Môžeš byť v ohrození", "see_exposure_history": "Pozri si svoju históriu vystavenia sa riziku", "settings_title": "Panel", + "skip_this_step": "Preskoč tento krok", "team": "Tím", "team_para": "Náš tím je zložený z konzorcia epidemiológov, inžinierov, vedcov, evanjelistov v oblasti digitálneho súkromia, profesorov a výskumníkov z uznávaných inštitúcií zahŕňajúc: MIT, Hardward, The Mayo Clinic, TripleBlind, EyeNetra, Ernst & Young a Link Ventures.", "terms_of_use": "Podmienky používania" }, + "onboarding": { + "eula_checkbox": "Súhlasím s licenčnou zmluvou", + "eula_continue": "Pokračovať", + "eula_message": "Musíš prijať podmienky používania na to, aby si mohol používať Safe Paths" + }, "share": { - "button_text": "Zdieľaj svoje údaje o polohe", + "button_text": "Zdieľať svoje údaje o polohe", + "paragraph_first": "Ak si testovaný/á pozitívne na COVID-19, prosím, prispej zdieľaním svojej polohy so všetkými miestnymi orgánmi.", + "paragraph_second": "Poloha je zdieľaná ako jednoduchý zoznam časov a miest, bez akýchkoľvek ďalších informácií.", "subtitle": "Tvoje osobné údaje môžu byť odovzdané zdravotným úradom, zálohované alebo inak zdieľané.", "title": "Zdieľaj históriu tvojej lokalizácie" + }, + "version_update": { + "alert_label": "Aplikácia COVID Safe Paths je neaktuálna", + "alert_sublabel": "Aktualizovať na novú verziu?", + "later": "Neskôr", + "push_notification_message": "Prosím, aktualizuj si aplikáciu", + "push_notification_title": "Aplikácia je neaktuálna", + "update": "Aktualizovať" } } diff --git a/ios/en.lproj/Localizable.strings b/ios/en.lproj/Localizable.strings index dfc62e51bc..de0916853e 100644 --- a/ios/en.lproj/Localizable.strings +++ b/ios/en.lproj/Localizable.strings @@ -1,4 +1,4 @@ /* Body text of notification when app is closed on iOS */ "ios.app_closed_alert_text" = "COVID Safe Paths requires to be running."; /* Title of notification when app is closed on iOS */ -"ios.app_closed_alert_title" = "COVID Safe Paths Was Closed"; +"ios.app_closed_alert_title" = "COVID Safe Paths was closed"; diff --git a/ios/it.lproj/Localizable.strings b/ios/it.lproj/Localizable.strings index 7c1690577d..209ddfca40 100644 --- a/ios/it.lproj/Localizable.strings +++ b/ios/it.lproj/Localizable.strings @@ -1,4 +1,4 @@ /* Body text of notification when app is closed on iOS */ "ios.app_closed_alert_text" = "COVID Safe Paths deve essere attivo."; /* Title of notification when app is closed on iOS */ -"ios.app_closed_alert_title" = "COVID Safe Paths è stata chiusa."; +"ios.app_closed_alert_title" = "COVID Safe Paths è stata chiusa"; diff --git a/ios/pl.lproj/InfoPlist.strings b/ios/pl.lproj/InfoPlist.strings new file mode 100644 index 0000000000..a322f9d82e --- /dev/null +++ b/ios/pl.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Wszelkie prawa zastrzeżone."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Historia Twojej lokalizacji zostanie zapisana na Twoim urządzeniu i udostępniona innym tylko wtedy, gdy zdecydujesz się to zrobić."; +"NSLocationAlwaysUsageDescription" = "Historia Twojej lokalizacji zostanie zapisana na Twoim urządzeniu i udostępniona innym tylko wtedy, gdy zdecydujesz się to zrobić."; +"NSLocationWhenInUseUsageDescription" = "Historia Twojej lokalizacji zostanie zapisana na Twoim urządzeniu i udostępniona innym tylko wtedy, gdy zdecydujesz się to zrobić."; +"NSMotionUsageDescription" = "Używamy twojego przyspieszeniomierza, aby uzyskać dokładną historię lokalizacji."; diff --git a/ios/pl.lproj/Localizable.strings b/ios/pl.lproj/Localizable.strings new file mode 100644 index 0000000000..c63acf31ac --- /dev/null +++ b/ios/pl.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths wymaga uruchomienia."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "Aplikacja COVID Safe Paths została zamknięta"; diff --git a/ios/sk.lproj/InfoPlist.strings b/ios/sk.lproj/InfoPlist.strings new file mode 100644 index 0000000000..fe3223fcea --- /dev/null +++ b/ios/sk.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +"NSHumanReadableCopyright" = "Copyright © 2020 Path Check Inc. Všetky práva vyhradené."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Tvoja história polohy sa uloží do tvojho zariadenia a bude zdieľaná s ostatnými iba vtedy, ak sa tak rozhodneš. "; +"NSLocationAlwaysUsageDescription" = "Tvoja história polohy sa uloží do tvojho zariadenia a bude zdieľaná s ostatnými iba vtedy, ak sa tak rozhodneš. "; +"NSLocationWhenInUseUsageDescription" = "Tvoja história polohy sa uloží do tvojho zariadenia a bude zdieľaná s ostatnými iba vtedy, ak sa tak rozhodneš. "; +"NSMotionUsageDescription" = "Používame akcelerometer na presnú históriu polohy. "; diff --git a/ios/sk.lproj/Localizable.strings b/ios/sk.lproj/Localizable.strings new file mode 100644 index 0000000000..4a60d7e308 --- /dev/null +++ b/ios/sk.lproj/Localizable.strings @@ -0,0 +1,4 @@ +/* Body text of notification when app is closed on iOS */ +"ios.app_closed_alert_text" = "COVID Safe Paths vyžaduje spustenie."; +/* Title of notification when app is closed on iOS */ +"ios.app_closed_alert_title" = "Aplikácia COVID Safe Paths bola zatvorená"; From 0fd622d8dc6270accd7a40e1bd671b8a03939315 Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Thu, 30 Apr 2020 19:19:00 -0700 Subject: [PATCH 55/60] Remove rebasing from suggestions in CONTRIBUTING.md (#724) --- CONTRIBUTING.md | 103 +++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b0205b308..08bef1144c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,13 +23,13 @@ We welcome participation in an open project. We want to make it as easy as possi cd ~ # get to your home directory or where ever you want to go -git clone https://github.com/YOURACCOUNT/covid-safe-paths +git clone git@github.com:YOURACCOUNT/covid-safe-paths.git # change into the newly created directory cd covid-safe-paths # set upstream against COVID Safe Paths repository -git remote add upstream https://github.com/tripleblindmarket/covid-safe-paths.git +git remote add upstream git@github.com:tripleblindmarket/covid-safe-paths.git ``` @@ -37,57 +37,57 @@ git remote add upstream https://github.com/tripleblindmarket/covid-safe-paths.gi ## Make Changes -1. Create a branch based on the `develop` branch on your forked repository. Name the branch something to reflect what you are doing. For example, if you want to add a new icon, a branch name you could use: +### Create a branch + +1. Always create a new branch from the latest `upstream/develop`: + ```bash + git checkout develop # you want to branch from the latest 'develop' branch + + git pull upstream/develop # make sure you have the latest code from upstream + + git push origin develop # optional, push these changes to YOUR fork's develop branch + ``` +2. Create the branch. Name the branch something to reflect what you are doing. + ``` + git checkout -b "feature/new-icon" develop # new branch created! + + "or" + + git checkout -b "fix/new-icon" develop # new branch created! + + "or" + + git checkout -b "release/new-icon" develop # new branch created! + ``` +3. Stick to the coding style and patterns that are used already. +4. Document code! Comments are good. More comments are better. :) +5. Make commits as you desire. Ultimately they will be squashed, so make notes to yourself. It's as simple as `git commit -m "commit message goes here"`! + +### Merge upstream/develop into your branch to get the latest changes. ```bash -git checkout develop # you want to branch from the main 'develop' branch +# if you've already done this it will fail, that's fine: +git remote add upstream git@github.com:tripleblindmarket/covid-safe-paths.git -git pull # make sure you have the latest code when you start the branch +# ensure you are on your feature/fix branch +git checkout feature/my-feature -git checkout -b "feature/new-icon" develop # new branch created! +# get latest upstream branches e.g. upstream/develop +git fetch upstream -"or" +# merge upstream/develop into your local branch, this will always create a single merge commit +git merge upstream/develop --no-ff -git checkout -b "fix/new-icon" develop # new branch created! +# you may need to resolve conflicts. If so, resolve them and commit the merge: +git commit -"or" - -git checkout -b "release/new-icon" develop # new branch created! -``` - -2. Stick to the coding style and patterns that are used already. - -3. Document code! Comments are good. More comments are better. :) - -4. Make commits as you desire. Ultimately they will be squashed, so make - -notes to yourself. It's as simple as `git commit -m "commit message goes here"`! - -5. Rebase your feature branch with upstream/develop to avoid any code conflicts: - -```bash -# 1. Rebase Base(COVID Safe Paths) repository with fork repository - develop branch - -git checkout develop # switch to base branch(local) - -git fetch upstream # fetch latest commits from "COVID Safe Paths" develop branch - -git rebase upstream/develop # rebase code against your forked develop branch(local) - -git push -f origin develop # push rebased code after resolving conflicts to forked develop branch(remote) - -# 2. Rebase feature branch(local) with develop branch(local) - -git checkout # switch back to original feature branch(local) you are working - -git rebase develop # now rebase your feature branch(local) against develop branch(local) - -git push origin feature/ # after resolving all conflicts, push your new feature branch to the remote forked repository +# push your changes up to your branch again +git push -u origin # now your feature branch is ready for PR against COVID Safe Paths develop branch. ``` -6. Start a PR to submit your changes back to the original project: +### Start a PR to submit your changes back to the original project: - Visit https://github.com/your-git-userid/covid-safe-paths/branches @@ -108,25 +108,10 @@ git push origin feature/ # after resolving all conflic - Provide a meaningful title and description to your PR, as shown in the above image. - Provide Issue ID on PR description to link/close the issue upon PR merged. +- If you are changing visuals, please provide screenshots so the PR reviewer can see what you've done without running it in the app. ## Helpful resources on Git -- Git commands: - -``` -git checkout develop - -git fetch - -git reset --hard origin/develop - -git checkout - -git rebase develop - -git push -f -``` - - Documentation on how to [create a Pull Request (PR) on Github](https://help.github.com/articles/using-pull-requests/) for review and merging. **Note**: Even if you have write access, do not work directly on `master` or push directly to `develop`! All work is done against `develop` reviewed and merged via PRs, and ultimately `develop` gets merged into `master` for tagged code releases. @@ -173,6 +158,8 @@ _Advanced users may install the `hub` gem and use the [`hub pull-request` comman ## Reviewing Pull Requests +- If you are using VS Code, use the [GitHub PR extension](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github), which will allow you to checkout and run anyone's PR with ease. + - Open the PR on Github. At the top of the PR page is a number which identifies it -123 and the name of the author's branch -branch-name. Copy down both of these. * Open git bash and ensure your working directory is clean by running `git status` From ba75fedc4c2e70f21768a5e288390117fb2b7583 Mon Sep 17 00:00:00 2001 From: Sergey Semashko <51369852+ten-sis@users.noreply.github.com> Date: Fri, 1 May 2020 09:55:19 -0400 Subject: [PATCH 56/60] Better error logging in Google import (#703) --- app/helpers/GoogleTakeOutAutoImport.js | 41 +++++++-------- app/locales/en.json | 10 +++- app/views/Import.js | 51 ++++++++++--------- .../__snapshots__/Import.spec.js.snap | 4 +- package.json | 1 + 5 files changed, 54 insertions(+), 53 deletions(-) diff --git a/app/helpers/GoogleTakeOutAutoImport.js b/app/helpers/GoogleTakeOutAutoImport.js index 8e4d2c30b7..40fed66d75 100644 --- a/app/helpers/GoogleTakeOutAutoImport.js +++ b/app/helpers/GoogleTakeOutAutoImport.js @@ -10,6 +10,7 @@ import { mergeJSONWithLocalData } from '../helpers/GoogleData'; export class NoRecentLocationsError extends Error {} export class InvalidFileExtensionError extends Error {} +export class EmptyFilePathError extends Error {} const ZIP_EXT_CHECK_REGEX = /\.zip$/; let progress; @@ -49,6 +50,9 @@ export function getFilenamesForLatest2Months(rootPath, now) { // Imports any Takeout location data // Currently works for Google Takeout Location data export async function importTakeoutData(filePath) { + if (!filePath) { + throw new EmptyFilePathError(); + } let unifiedPath = filePath; if (Platform.OS === 'ios') { @@ -93,30 +97,19 @@ export async function importTakeoutData(filePath) { if (isExist) { console.log('[INFO] File exists:', `file://${filepath}`); - const contents = await RNFS.readFile(`file://${filepath}`).catch( - err => { - console.log( - `[INFO] Caught error on opening "file://${filepath}"`, - err, - ); - console.log( - `[INFO] Attempting to open file "file://${filepath}" again`, - err, - ); - - /** - * IMPORTANT!!! - * A temporary hack around URI generation bug in react-native-fs on android. - * An exception is thrown as `file://` is not in the file URI: - * "Error: ENOENT: No content provider: - * /data/user/0/edu.mit.privatekit/cache/Takeout-2020-04-12T15:48:54.295Z/Takeout/Location History/Semantic Location History/2020/2020_APRIL.json, - * open '/data/user/0/edu.mit.privatekit/cache/Takeout-2020-04-12T15:48:54.295Z/Takeout/Location History/Semantic Location History/2020/2020_APRIL.json' - * " - * @see https://github.com/itinance/react-native-fs/blob/master/android/src/main/java/com/rnfs/RNFSManager.java#L110 - */ - return RNFS.readFile(`file://file://${filepath}`); - }, - ); + const contents = await RNFS.readFile(`file://${filepath}`).catch(() => { + /** + * IMPORTANT!!! + * A temporary hack around URI generation bug in react-native-fs on android. + * An exception is thrown as `file://` is not in the file URI: + * "Error: ENOENT: No content provider: + * /data/user/0/edu.mit.privatekit/cache/Takeout-2020-04-12T15:48:54.295Z/Takeout/Location History/Semantic Location History/2020/2020_APRIL.json, + * open '/data/user/0/edu.mit.privatekit/cache/Takeout-2020-04-12T15:48:54.295Z/Takeout/Location History/Semantic Location History/2020/2020_APRIL.json' + * " + * @see https://github.com/itinance/react-native-fs/blob/master/android/src/main/java/com/rnfs/RNFSManager.java#L110 + */ + return RNFS.readFile(`file://file://${filepath}`); + }); newLocations = [ ...newLocations, diff --git a/app/locales/en.json b/app/locales/en.json index 8a230a2c91..223f814119 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -21,12 +21,18 @@ "button_text": "Import past locations", "google": { "disclaimer": "Safe Paths has no affiliation with Google and never shares your data.", - "instructions_detailed": "Visit Google Takeout and export your Location History using the following settings: \n1. Delivery method: \"Add to Drive\" \n2. Frequency: \"Export once\" \n3. File type & size: \".zip\" and \"1GB\"\n4. Google sends an email when the export is ready \n5. Return here to import locations. Import options:\n- Import from Google Drive\n- Download from browser, then import from local phone files. Make sure to be on WiFi network as files can be big.", + "instructions_detailed": "Visit Google Takeout and export your Location History using the following settings: \n1. Delivery method: \"Add to Drive\" \n2. Frequency: \"Export once\" \n3. File type & size: \".zip\" and \"1GB\"\n4. Google sends an email when the export is ready \n5. Return here to import locations from Google Drive", "instructions_first": "Adding location data from Google will give you a head start on building your recent locations.", "instructions_second": "Before you can import, you must first \"Take out\" your location data from Google.", "title": "Google Maps", - "visit_button_text": "Visit Google Takeout" + "visit_button_text": "Visit Google Takeout", + "already_imported":"Provided Takeout file has already been imported.", + "no_recent_locations": "Takeout doesn't have any recent locations.", + "invalid_file_format": "Provided file format is not supported. \nSupported formats: \".zip\".", + "file_open_error": "Could not open the file. \nPlease, make sure the file is opened from Google Drive" }, + "success":"Recent locations has been successfully imported!", + "error":"Something went wrong while importing your data.", "subtitle": "To see if you encountered someone with COVID-19 prior to downloading this app, you can import your personal location history.", "title": "Import Locations" }, diff --git a/app/views/Import.js b/app/views/Import.js index c997d47706..333c6d6842 100644 --- a/app/views/Import.js +++ b/app/views/Import.js @@ -9,13 +9,13 @@ import { View, } from 'react-native'; -import languages from './../locales/languages'; import NavigationBarWrapper from '../components/NavigationBarWrapper'; import { Typography } from '../components/Typography'; import colors from '../constants/colors'; import fontFamily from '../constants/fonts'; import { pickFile } from '../helpers/General'; import { + EmptyFilePathError, InvalidFileExtensionError, NoRecentLocationsError, importTakeoutData, @@ -33,41 +33,44 @@ const ImportScreen = props => { const { navigation: { goBack }, } = props; - const [importResults, setImportResults] = useState(makeImportResults()); - + const [importResults, _setImportResults] = useState(makeImportResults()); + const setImportResults = (...args) => + _setImportResults(makeImportResults(...args)); async function importPickFile() { try { // reset info message - setImportResults(makeImportResults()); + setImportResults(); const filePath = await pickFile(); - if (filePath) { - const newLocations = await importTakeoutData(filePath); - if (newLocations.length) { - setImportResults(makeImportResults('label.import_success')); - } else { - setImportResults(makeImportResults('label.import_already_imported')); - } + + const newLocations = await importTakeoutData(filePath); + + if (newLocations.length) { + setImportResults(t('import.success')); + } else { + setImportResults(t('import.google.already_imported')); } } catch (err) { if (err instanceof NoRecentLocationsError) { - setImportResults( - makeImportResults('label.import_no_recent_locations', true), - ); + setImportResults(t('import.google.no_recent_locations'), true); } else if (err instanceof InvalidFileExtensionError) { - setImportResults( - makeImportResults('label.import_invalid_file_format', true), - ); + setImportResults(t('import.google.invalid_file_format'), true); + } else if (err instanceof EmptyFilePathError) { + /** + * If the imported file is opened from other than Google Drive folder, + * filepath is returned as null. Leaving a message to ensure import file + * is located on Google Drive. + */ + setImportResults(t('import.google.file_open_error'), true); } else { - setImportResults(makeImportResults('label.import_error', true)); + console.log('[ERROR] Failed to import locations', err); + setImportResults(t('import.error'), true); } } } return ( - + @@ -90,7 +93,7 @@ const ImportScreen = props => { } style={styles.buttonTouchable}> - {languages.t('import.google.visit_button_text')} + {t('import.google.visit_button_text')} { onPress={importPickFile} style={styles.buttonTouchable}> - {languages.t('import.title')} + {t('import.title')} @@ -108,7 +111,7 @@ const ImportScreen = props => { ...styles.importResults, ...(importResults?.error ? styles.importResultsError : {}), }}> - {languages.t(importResults.label)} + {importResults.label} ) : null} diff --git a/app/views/__tests__/__snapshots__/Import.spec.js.snap b/app/views/__tests__/__snapshots__/Import.spec.js.snap index d16de84915..d6685f521c 100644 --- a/app/views/__tests__/__snapshots__/Import.spec.js.snap +++ b/app/views/__tests__/__snapshots__/Import.spec.js.snap @@ -165,9 +165,7 @@ Array [ 2. Frequency: "Export once" 3. File type & size: ".zip" and "1GB" 4. Google sends an email when the export is ready -5. Return here to import locations. Import options: -- Import from Google Drive -- Download from browser, then import from local phone files. Make sure to be on WiFi network as files can be big. +5. Return here to import locations from Google Drive Date: Fri, 1 May 2020 08:59:43 -0500 Subject: [PATCH 57/60] Update dev README (#705) --- CONTRIBUTING.md | 4 +- README.md | 140 ++++++++++++++---- assets/Safe_Paths_Logo.png | Bin 0 -> 18154 bytes ...it Diagram.png => Private_Kit_Diagram.png} | Bin reset_react.sh | 11 -- 5 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 assets/Safe_Paths_Logo.png rename docs/{Private Kit Diagram.png => Private_Kit_Diagram.png} (100%) delete mode 100755 reset_react.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 08bef1144c..1e288b5ce7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,9 @@ We welcome participation in an open project. We want to make it as easy as possible for people to work together, so please follow these guidelines to prepare and submit a pull request. -## How to prepare +- Filtering by [good first issues](https://github.com/tripleblindmarket/covid-safe-paths/labels/good%20first%20issue) is the recommended way to begin contributing to the project + +## Create A New Issue - You need a Github account. You can [create one](https://github.com/signup/free) for free. diff --git a/README.md b/README.md index 9695c39f35..924892a307 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,160 @@ -# COVID Safe Paths ![Android and iOS build on MacOS](https://github.com/tripleblindmarket/covid-safe-paths/workflows/Android%20and%20iOS%20build%20on%20MacOS/badge.svg) - -**Applying the technology and philosophy of Private Kit to COVID** +
                +

                COVID Safe Paths

                + + + safe paths logo + + +

                + Applying the technology and philosophy of Private Kit to COVID-19 +

                + + **https://covidsafepaths.org | https://safepaths.mit.edu** +
                + +
                Help us stop COVID-19. -We’re building the next generation of secure location logging to preserve privacy and #flattenthecurve +We’re building the next generation of secure location logging to preserve privacy and _#flattenthecurve_ Location logs provide time-stamped records of where users have been, allowing them to share information with health officials accurately and quickly. This helps support contact tracing efforts to slow the spread of the virus. -What’s truly special about Safe Paths, though, is its privacy protection. Data never leaves a user's device without their password entry and explicit consent. The location log generated by Safe Paths cannot be accessed from outside the user’s device, meaning data transfer occurs only if the user chooses to share it with a researcher or health official. +What’s truly special about Safe Paths, though, is its privacy protection. ----- +Data never leaves a user's device without their password entry and explicit consent. The location log generated by Safe Paths cannot be accessed from outside the user’s device, meaning data transfer occurs only if the user chooses to share it with a researcher or health official. -Safe Paths is a ‘privacy-first’ app that allows you to log your GPS trails on your own phone. The information is stored locally and never shared with anyone (not even with us or MIT) until you explicitly decide to manually export the data. The location log generated by Safe Paths cannot be accessed from outside the user’s device. Location information can be imported and exported by the user and used in other projects and applications. +## Overview + +Safe Paths is a ‘privacy-first’ app that allows you to log your GPS trails on your own phone. The information is stored locally and never shared with anyone (not even with us or MIT) until you explicitly decide to manually export the data. + +The location log generated by Safe Paths cannot be accessed from outside the user’s device. However, the user can import and export their location information and use it in other projects and applications. Safe Paths logs your device’s location once every five minutes and stores 28 days of data in under 100KB of space. -**Home page:** https://covidsafepaths.org and https://safepaths.mit.edu +### Private Kit WhitePaper + +[Apps Gone Rogue: Maintaining Personal Privacy in an Epidemic](https://drive.google.com/file/d/1nwOR4drE3YdkCkyy_HBd6giQPPhLEkRc/view?usp=sharing) + +### Downloads for COVID Safe Paths + +_coming soon!_ -**Private Kit WhitePaper:** [Apps Gone Rogue: Maintaining Personal Privacy in an Epidemic](https://drive.google.com/file/d/1nwOR4drE3YdkCkyy_HBd6giQPPhLEkRc/view?usp=sharing) +### Downloads for Private Kit (technology beta) -**Downloads for COVID Safe Paths:** _coming soon!_ +[Google Play](https://play.google.com/store/apps/details?id=edu.mit.privatekit) | [Apple Store](https://apps.apple.com/us/app/private-kit-prototype/id1501903733) -**Downloads for Private Kit (technology beta):** [Google Play](https://play.google.com/store/apps/details?id=edu.mit.privatekit) | [Apple Store](https://apps.apple.com/us/app/private-kit-prototype/id1501903733) +
                # Development Overview -This is a React Native app version 61.5 +![Android and iOS build on MacOS](https://github.com/tripleblindmarket/covid-safe-paths/workflows/Android%20and%20iOS%20build%20on%20MacOS/badge.svg) + +_Safe Paths_ is a built on [React Native](https://reactnative.dev/docs/getting-started) v0.61.5 + +## Contributing + +Read the [contribution guidelines](CONTRIBUTING.md). ## Architecture -Please refer to `docs/Private Kit Diagram.png` for a basic overview on the sequencing of generalized events and services that are used by Safe Paths. +View the [architecture diagram](docs/Private_Kit_Diagram.png) for a basic overview on the sequencing of generalized events and services that are used by Safe Paths. ## Developer Setup -Run the ```dev_setup.sh``` (Linux/MacOS) or ```dev_setup.bat``` (Windows) for needed tools. It is assumed that Android Studio and/or xcode (on macOS) is set up and configured correctly to run applications in the simulator. For Android Studio, the dev_setup script can help you, but you still may need to configure at least one android virtual device (avd) from within Android Studio first before you can run the app. +First, run the appropriate setup script for your system. This will install relevant packages, walk through Android Studio configuration, etc. -## Running +**Note:** You will still need to [configure an Android Virtual Device (AVD)](https://developer.android.com/studio/run/managing-avds#createavd) after running the script. -```yarn``` must be installed for this project, as it does a better job installing dependendies across platforms. The dev_setup script above should have done that for you. +#### Linux/MacOS -### Install modules to the correct locations +``` +dev_setup.sh +``` + +#### Windows -1. ```yarn install``` (always do this after a new clone or checkout) -2. ```yarn install:pod``` (additional step for macOS if you want to do an iOS build) +``` +dev_setup.bat +``` -### Run the app in a simulator +## Running + +**Note:** In some cases, these procedures can lead to the error `Failed to load bundle - Could not connect to development server`. In these cases, kill all other react-native processes and try it again. + +#### Android (Windows, Linux, macOS) -To run in the Android simulator (Windows, Linux, macOS): ``` npx react-native run-android ``` -or (on macOS only) + +Device storage can be cleared by long-pressing on the app icon in the simulator, clicking "App info", then "Storage", and lastly, "Clear Storage". + +#### iOS (macOS only) + ``` +yarn install:pod ## only needs to be ran once npx react-native run-ios ``` -NOTE: In some cases, these procedures can lead to the error `Failed to load bundle - Could not connect to development server`. In these cases, kill all other react-native processes and try it again. +Device storage can be cleared by clicking "Hardware" on the system toolbar, and then "Erase all content and settings". -## Contributing +### Release Builds -Read the [contribution guidelines](CONTRIBUTING.md). +Generating a release build is an optional step in the development process. + +- [Android instructions](https://reactnative.dev/docs/signed-apk-android) + +### Debugging + +[react-native-debugger](https://github.com/jhen0409/react-native-debugger) is recommended. This tool will provide visibility of the JSX hierarchy, breakpoint usage, monitoring of network calls, and other common debugging tasks. + +## Testing + +Tests are ran automatically through Github actions - PRs are not able to be merged if there are tests that are failing. + +### Unit Test + +To run the unit tests: + +``` +yarn test --watch +``` + +[Snapshot testing](https://jestjs.io/docs/en/snapshot-testing) is used as a quick way to verify that the UI has not changed. To update the snapshots: + +``` +yarn update-snapshots +``` + +### e2e Test +**Note:** Right now, there is only e2e test support for iOS. + +e2e tests are written using [_detox_](https://github.com/wix/Detox). Screenshots of each test run are saved to `e2e/artifacts` for review. + +To run the e2e tests: + +``` +yarn detox-setup ## only needs to be run once +yarn build:e2e:ios ## needs to be run after any code change +yarn test:e2e:iphone{11, -se, 8} +``` +### Manual Device Testing +Mobile devices come in many different shapes and sizes - it is important to test your code on a variety of simulators to ensure it looks correct on all device types. +Before pushing up code, it is recommended to manually test your code on the following devices: +- Nexus 4 (smaller screen) +- iPhone 8 (smaller screen) +- Pixel 3 XL (larger screen) +- iPhone 11 (screen w/ notch) diff --git a/assets/Safe_Paths_Logo.png b/assets/Safe_Paths_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4fab91bd7bddc6dc460ef0fcf83280f42386f5fd GIT binary patch literal 18154 zcmZU)1ymeCvnY%O3Bd^(+(U2+4vV|HYjAgWcXzV56JT+NV8Ir52=4CwHs8JPzI*prBnKz=;SC>2bWaWQT(KBx@-mq9`RI zLaOLwZ*FO01_dPNNdk*(?eo|&D8`Qzv zg;~9iDoFs0C6JGdHHCwbAy>=`PVpzgLBPkVN0`tUED{pfpPxG3>1u1CMQ^)KYPO!5 z-n*k9TA)J({1(ZfQbqhox{eH`Po6M>3{6@OT(`3V%7(I$awhddk)@S8#KEUmIHbXA zl-nj@wxFLw1;}n+?{Y&OaEkC(N+!U1y4VYykbRaef;vbfL&c1E1Vj{)5^F0mYlQ-d z944NEX!c2Rw1RVd{3b}}cM5QvGDOKn_Z8j+M=!gJNe#IF_G55nX%eUY{DVJ#D!#sB z@wr5Jm0>(8Hh2+zs~HoxOl+L;Rb8CQ`a}siFbLTiJ_Fqw@zH1sT}S8(*?4^H)KNn{ z6$W3#<|pA&sgIsg%XP@2Q5T{NKVp5H0IN_FY0lisbtpcq%e8;rT3DGf3wOK8a4z8= z3_eFEE0dML1SmyQ($%3K18jbu)iPZ4etbNfO{!Y!0#3?_wZn zdus(6lNwaXe%lHG?=IfzQj)}$}7Nh-$ys#737+Ey3 z>{I7m-v@>}T_o1>>zF5|iN2yx^U{eJ|11`{T=!50_)f<0;t^BDEFgtNlb1P4LR1@x zzy!k*fZPX#Olvd>lPt`b0CMGW?KjBEL+n9tN`MvZ(W}JAORP9c?E67Z3-1r0go)~P z1Rm*Q&;~ul2WyYd5SdnbN6%7R^_WB;3$YJ}cc&gHO$*!8{l%D-?7CzOsN3r=IeJzf z3e3W*7U%96ohJN68HUv#q4XQE0NE}MVH4WplTTRBUSxw~cc6vf3KlWeG7=i11`<(7 zb}}W^Rjn~TEDx!F2dD?uU1hEFaa+Q|bkr&6QP&F;u6nMW2wJM2U+V?3G#lV6{}7!#E(_jA^B`~XK3U7y zKsO=qh7t`-7$W?l?&(7{8DI1~`S}rT0w{6B&Hk4`m`H(mC(&m+zV^1fqNuNMy>N6p zxCsqAs-*l*q9^xTHe>ou1hUqbmg{IuzH-Aw#kA7!QUs`q^9b{7uMt&$67;>L$+jd7 zD7cT$&(CN+^8-B`o%g&gV|x&`Ke>oaj7Qw`rGlO?X7oFBIT-dV~IoU(j42 zZX`s3u4m$!jRiD2d6Mut+eyr6`l| zM`E!hqbM$=LW@|Z38qn}ji#xTX$~2*!foX4C?yhRMwFe9T9Z0*eIk4!UqV(Trb@pn zzR)>hlbP|DA(^oihdKpLOC1&xDCU+0&Qe-2x)L`=Eer7rcZ&Fy7Z#dSZkMvDOBAqa zluG|dpRmk*<~g?rW7i-^PZL&|#+hlFjsx3G>rWrLNO&Rg=juxu$krB;6mArD&zT*d z&g>q3blpEtKju8rpZ2Yhm~sYhAIQzp&sWZ_6uJx7>f{%kmrc&?A5YHL&TrKE>vb;# z7%}&ePW9z|!~W8-9#XXc5G5SJI{ zE7UFgSePHX9OD&(5-UMail@L%^y79>b5V4O)|PV_v~;t?vqZg;ws;2Ww3cWzSutCw23a>A*EKhh zm}_Ji|-5atNZi&=fA`WxWD-S9@3U76)0s(@P7iK>>${EIzcr4UI)@(~M7emeViK8PkcAv3F${~A^>&S3O>e9LUk^cJxtXfEhSANr=W zQ97+6NcV=4m)YC%ZYHNE=QFq7mZ4*Di%wr^Rcgm@?4PxSXKE%!dux4vKdZM17$;%# zUcYi6yMmWb{oIaa@`{;VJ*(Cb_B&l=$<1)#t`2sDaZo`k7?F5!l0G+egYF#n!)Gy ziiMs)qh39&)~C^Gx`Hj0f}X3Yq(;5wtSqYBO5dd0{4fj6dce9?JzV{~s;*2+^V&Su zVXgRK)2?js;!R>Co{Ll5&Zf$>s(Mv)RTXr2xtB6t>Sz3}+C1PRGmsXqQ>??= zL}kOPzptawyk`8IYy!tDHE{paTp;x0#1v( zhDW2_6DJZK3S>Fo7#$bQuM7}xAv8;k41#BOlYzBnyV@d_^fDb|==w^(_)kW{sJ;XHgz@SXIs_cS~x ze3F=s*!SR@udKiBOygf{Xv#y6I?_=z9&KYZ8B{0UyPu2&x)*Si64JR58^ zrPrY^V(%N#`dOW;PgXmX^Xm`m-nGd3_nmb9)^~0zym##?=PDb5huKeoH^8I9(V;Yj zsr<7W+?)H+2dAzl+JN3L^B=Gm_v%mwmQc)$eb@aSzgKLo^Iy}fn0 zzP^%h^&8s5IG9H5`ITdmcM!hd6#z8kLY;dxu9%Wp{V_byO5h;jMUB2rS2QpLo{%*@W&!rmp@3cmx=f#e{m}?s1Ozn-$7(Hwq{_%n0^WcG0ZOvSaNIh(A?3{T#_{sl+ z!2_xP>t-S+{SS(ZH9xtgoFb`+y^|R!2O~QpGr7P=Qc_YrCsT7CWl{0}CWo~6$t_%5 z9C(1xBWmVs;$-RI zVrg$j`j4-XvAwGcKRNk7LH~38m!4)Gmj6$Zo%4U+7UTw*{*5rPFfud!4{r!5-@jfS zMN1De8%EE6IFH8KFng7!Zv9rKOKBoVfGl7q@9n>a}Y5Za-s-Oxf zA&cxkS2pCA76SiD2oQY$NEaSM(59qBg;hPEPxP!~)YQ_})jo*Hd{!n$nWcq?)}dxt z1Ib78V|qJ$DYn(-EyTz21I3e2i@&hMA-(m9?jvC8_y<^SQ^I6DSh38+ZWqol5EXAz zP_PCk8-@fyOF`@PD$^oasg1wMJ9^pipLOo%b>1yM=R8;MIN7Z^Px$$G=f}Iw>sz0u z8PSIU;3A+fNMS-HeujqVKd_i0{HKWoL+K(9M=k^xAtZ%P3QRX8T|)WSfuTv0VxQNSmaKS6n=&U*y+I_>ju;QisJ9vh@xPJsUh3ThmuU#ut}I!SPu-=t zAv)mNe2M?^Jb1$Iu#9>4lj_)hOi5<(JMrT+qT_aZbkT2t{gq^_pnNv#`b>W7B(3lE zQP7Xul2a0fe@Bh3m-b0*OfHB>67G8Nw+1&DVU+FjSB-f};?Viyh*WgOxMy@Qw}uii z1z{*}B!>_QI3v8z-)7A7Ur+ci)Cz1%v)qTxnK~SzGOtgi%mewicqRSfsC=V%VZinA z{-{^PoUbEOnp?!25B#8jS>c-o^EmX^PtRP9&)`DVHb2ZK3%;P&593}#&oo$9>$ifX znAN)P(+xO=tIbmB%DN)Ip zvha(ahFfz3?@-5m?cA-MTX4t|33b7-7+(~IwVYf1&gEyCw?4h*s1(8w7&?ZI%oPjOr+G_ zk)?Sk$0)Xxq4c8%2@c2hg*Y9in+5%_?A(fw-_}`N7XxIWU?`3_cK<$%17=XI9*J5{U+hpT+^)*Uaaiv+N=NQor70 z{K)Qn%&;#mazc8=uTD6GutDdC9zeKL@B!4w6~&0uDg0Y<2L*=O#nZxALUb?BH#oBS zW2CNyA{%ycmdRf#k@9#bq)q|xkMda$4kL@Y7;e^+bu-nS1PNcaQ9Rz_$h-6XPi`C7 zAldzC5zG19@9-3HN70cZ3Dp6E_aEI1xiKfd+T z7p9{{bY!0HE;cv#I?1rkf`u;cH3QKEik9wq7%0lqQ244HtcGGu({~NWSMFe+AJUiukJof8i5J@w6nqE|SM)t`@Q@RB}(Yj}?xb|+r7mA*2!_(fgse-!~| z<-YnAHm6Qe@<*XGH{{8l(p$}MGch4!m+ttY!K<5T4``!5K4aFfCU|iC9ThqgqrL@T zc`1#2p-!RqUo=wJ^>y21zqCSgYTOV$tF?z@B^KX87Xi01&dbRGz=Fq&ejL>L3?p2Y zM%&G~!J+;0{N)#j>v0~cvrtx5X8H!iO&l|l@2%k>y)noB4cC%o8YR959>iT5XnLHl z!mN~Az1I!FMRA%C@n%YrDv2o4mjp0jX7Zg@=@DB6;S0xxr|ylMj*xyZ$? z%%viI5@V%p;BEPizXMDVT5&@cqs5OxDO_5p*F86YRo66&u9R0#U@pLk7@0Gs=d0t; zXEE|S@2-!4mekG?@xhikw;@+U#aNjS1bnMkTgax%WAxA?$ z?%c*Tg5KNFm7eZLw-mZ{5CMXYPQzAv2t)h_Z{aLDC>JE%&v%(}?TeWx#U~m@moCw3 z`?te&_DPgFb$jN*!>`5BD<6*q^L#~JYGiR0yio@aKf@}&k6g8I0ps$(EFkNJTLMwa zL2Bk&LwC<_9?GT|0r-2F)v%m@OH`T+1E&r3AM;!VzloH!Kz-Q~`)v~@3}8y&8Ri#{ z%mbT@|3F<3Ir&WzP6`x4TquF;7VqyM=Z;xIvLcjL%s;$?z#2cI(5iJ;7~_gMV5KueT)Y^-Lmim} zHqnLaoCrItZ9>imgX!MTJ$~%}l)J&6ELaSkS39-GVf#M35%GfwpBm4{I1vw5s*v(# zDF29!sdJiSv?a6sJYytOA#^I@G_W{Q%m_?a1yk<5;l)IZ^@)@4apcNFmT3y(>6r7& z%O;9rQD3fkqhp3<7RSAG@MPA^ZvGOHlYT=<8XlbfH;-IKE1nfR=9?w>3`4(e3S#B> zkv!thz#(itIPb?ov^)hSoP347%zBgh?D!M5C2=O4P)GN27=7k*?Q7j{nO`tmu!CXH zQGjSV4Tqi9XxVyh_u1eeAz*lwxqP~4ZsLQX`$BJrnz{gPVW`o9E67of3*FFEAPv4`*tozP5{D0+rBooUTOPt3q|Eiq6;{Uv>3lF42bNEz5Jbid>%jV%hLV@6-6nddRj$#=m4}%^pAqqW-`2h7)O-_q|+}=*NWt33qJj4Wgtft0}<~PfJ`Y2Q1A*k z17~5+TrGbPfvD%Y5U3O_k<}kV6lT{z5+wjb*63W0Kz|-auS+_YZ*Lh31{Y4{ zfuI-XHZRJF<-LBk%_uBwUN<0Xye6!5RcN9T-lJQdomQc|iPxkQi`qkPH?p)mv*ecvpvVo7mhyhN8u;${Y>>2tH)bcZW5fh((cfDA{Z6piaIj)jo*)A!6M(nlzHV?XIMKTf>u)x{uU?gS5(~?>U(R+SkW<4A{ZRsX8h2}jwKV9rQJ%FV5Z1i)(a}nWZ zy_>Cbbi7zGNN`!3XPlAlYBN5|K6Ba}*YO{lm%m93V})+kOwt~3?43GoB_=fF{@g4- zGgdF6^TIfT-0-2j+FO3oc%Pr~i@=jGB&y2(M$%hZtvA2@*06Qd(D>^IzZyE&Ppxxn zTHV^)rh1wnGoP#5VU;wtCXIPm68UiXmUL)T#*INzhEX4E#i;X*EghmA8wotyS1Y;J zr&|kaj$zI41-1MjQ$6r>V+cN8V?vEV{N7fXbr|^1dITnqzKX`0s}PkA5l`;`_z&>Y zv)(oIMI#;$j_~O%>7E-tFU;oKyx(@p)jTGZ%Q&2bY$BmueSEdn^Jlf`z3)v-ChvZh zI^CW=DjSRWov(8DV)kH8)n9H_dKtQgUXGuY01G)U?9DBxovW`|%P~90mN;;raYnT2 zL(b7OH&aW3+RK=pc3uRzNnvX@MfCD!c@}^~^Fqxr2k$zG;z1V+h^wL}ryNu6xpnsU zjhwhYM+d$Oq(d-jPL7nwX9y<%C{&u6^mS=kzHa^R0|MX^0S>HTgL zhg-DY$U|yc9e%GeEUUD+`I2a!ejq%xl0^T~gz)8FvJ^z)s-vmU zvKD4fdF4B8C?O(>Z2QG59SI99$J=(SQ-%n%E+HuPWr>rDQ@0S02m|GpM%1o2%${+1 zk-yFyU$)ZhWpX#Xm?hsVKO}vwX}vfnVEGC-(8k9(^e0|OF2MKLtZ0=aEj^5%h){~e zNTwG#mYd~pkPiWK@%yjxy%xN)7iyyWIv)dCm5in25iPu0bnUr<&axPr)imhx&wAwkGtc37|aAW>b4%#<9YGK*S;_LFD&7&?S6Lgsa*4$ zifR?2mpZUBq4I5>Cp_E|tA@|#+mb!dmWnQMZOYmZ7AS+cRZl^7~P=g93(v%lg$#GC7Qg!)U0Sx#>%&8O^eRNf;CE)!zpKah0#iST449x z9HtAl0H^IdE)}|ut9|6PedGc+a6f#Lh_Kg z47e@|XIhz|Sx%R14VI2{sTS=#9@6+<%|4+jLOr_g4>?0%4zC3d8C zQLSja>ITE(k{t>{>*yV>#|&+;WpI| zf5$2XU~oh57w&s;=H!4GQ1WAz+$0H?Lid5cJ0mE){j*eTmMnLzEeU(RuN|%upUi@b zNsR$<2Dq{BeY5QW5`v(xyGnmSv}kqG>-afG+C!W2S4qFUX5qxcU#J-A)3Zh(w=-h!!7oTI5|87_HmBu}aUnf=tgB z*csZsGX8r}JWYlwEsY#$XQkP)fQ}1FycvU|bbl0|ylSuOS~OZ`AwwM8&E#nw#|Yo^ zZ@wiA6U$Z%@RgVZte*P2v-`zZZ2rw+v*Pi1F`241yW)g?xVk92_Rdyt+_M?t8cva! zf$2x6Pda=BhiwNuOL~ekHx9tjf>FtljmlItYzB8VZBaxbl&LEhVZEFSAu`k{5K~)M zTpMW#clTla0jqNTz14)h1{&^-ZVb4QPY--R&vefG?&Z(br3^#yGjrSc?E?e+S@beY za)G)s<4r7M^fHBRr+{2?!SB9qJfO9i9^B1^g#Vq!bxXF)t!J5+_~;DPY+uO!j&sBx2U8Anp|1$ic4%;+QUKAOslP#t+Z611*N8(GBJ%Uv+M6; z1iIU3AG~t%gVH3;us&0{xaiLq;0lX(`+5m<>VeGlV!fiUIs}mxgofN>kjz_U`K_z(cS1$qCXgE&yb#{ z%p24G?Xt4W7FJ9QiNN9^C_e0pc$!}|;fGC|VS~~P!5oiJTAZ@~l`X!%RzgoNzI=DR zZ!_J>&H4C+>I+O(6p|NRC-qJ!m}3SC3VWG25a2NtQ!#TPv2Y`ZkQfa97X#>jN@Z_` z;${k|v2`=ru~-Zzt=41f8Fr-z0FBSZUD$*FkVbJbE7mMa9=z5N;Ya03W$7s9;Q}Oj zm(6jl4u$xuEa35^OiL<5)6BmjgMy*O8pTESbDG>357&~&{{A_Qk;mJ$+7 zzI(2Z4)^7h{RKE#pJ=B6URz!Oi~Z?6oSQL1ptbe)gHMevKhf3MPY(pvaJl&Ae(3uZ z8+?#mo>Vie(TZ$xiyM5CXjC?c}HXbNk7I zndr-{XwJyLo&+K1oA2+AO~#NE9d4fL*;f$_xaQy6h-@w`W`vK!nq&%olnhPz zt{i|9C`-?bl)>4^9QD;EAxhLn0FFF|$q~+{7sFBDcS>4Cq!cAV#G?=yUP7?Sld8jV zi@F?@B1#Em+Ns9q`&Rhw!(;1p``PlbulM;{Zf>r#`MG^j=~_E)yYn8Gv;X-j+kVa= zuTgY)`ODcwiJvWB&U;p95j9&Z1TwLJn#4#z--ShJiR zkuPV9^(4!6WBA+9K0R-j^=_1w`DXRLYgK>tE!o9c+P3*nY2%b(&0nsubFTEs)5%Va zU1NjXkU0O(@jgFFm!94dzp|!zHBHXzU;n1sYqOr9yZu7{Ed2L=D8_TQn#FmHSR_TT z(=sWOKSJqVgR{eT*vI`eq8r!Eb4Kv(l+SspYDCZK(v1i)#?Q^EYyR>~{=DP4Ny_}M zZud3L>74X|1(fMqtQxN)L6xEm8oaBW2w`=YfHB7{`tDV$?_StratFs}ZKT$#`iAih z|8r>81}c^aSvP@8pt8yfuxBkFcQ|Hy=-)UGu!N)nQ}b%Iz+R54&BH4Kmw0bNjcq zD8W-sQ04{Gd6tp-s^H>h74?hwp0{9{u`oHKv0%%?(*i z-L;;NbEqJ^tds-pCXyW*%6avhFM3m5Z=eKkdD6! zBNAy(E)YLamZhQgq%me%rY#AkT_AQ`%{xAuA(^pniqzES!;hf?7kZ`F_Y|FAGYx(uIL1p!F32Xhgpf$57kC zs3b%9yMDlO3ZP>DQsNk^}|V>x8ahA3G&fY>BmUS(t;5adism;)BE28`VF!W=p)Oqz-6!$=00STsae z%4i-Kk_$~9fT6IM=R#%yw4#W`xrNn#A@f)aa(Ns3@MI)2T&AQUM+-{o^~7^SE#QcZ zPy`+G&|Fr@V34`^1QNgO*#|ur!~Wbw`;;}{KH<M%^oWa-R7A=<-%>pUgKM~446&d~eQPAo|J zA*V1L*ys$R6F*R%Dm-BKzk&&MpbA+s8+0-0>iFpc9`v{2o&JMJ@DXLAQ&BSCM&i$G z(7cHCzJ*E*c&a5OEw0JZ3;LsxG-5;aXgb1#btUYOICBQ=SdIvNQVGt&ho_1INZtT+ zgV6nG3C?dvGGy z3i@PE%(0>0Nzb72%lYjE1>SX(q_c*!P!mUld!ui>86|D(p5)VlejC-oV}@V&aqD-W z%mY+uZ_p6(7}K{~a>XBZxVW~6*Qj~wEAC&ao43oGXI7J73I|onbr}vYaR70c$cV_YVb#XbNFudV%C7CK&*p)tO+;EUre9!r)JjvCXd?9K}gQ5FhM@gi{z ze5CgG zkT?@7oj!}s<3-jg6m@s_2NC;-LBMxf&wo8E?o;QmT{z-7PTCWC0w}vsBMBL3sj7z2 zb^~LX&rv3Le^U&9=%#ff+_MiqT@wuiQixdEmH%L*vQKTwmSGB^{{FWt3QU$@nuiNg z@a9o0t9`hVa-WL;NQA*iJ>@NMOvQMkCcyjCKNu?a5gXymtdrN8y* zkyWVnhA$1M@!E@py|J#=XywSHmPjvz`;HaY#JZIIt)E43PHMuEf`8!M?N{0~48&5?0t`9L#;c|OO;*0U_ z<$N%a0i}GoIL>+WWR`?TS>UGc$XZ0a@OKKvQFq+8yatXpHVzDQ<+tGKUwo5l$u++c zhehg0z;rzl@?;ZQfA)G@hPD>*Ut6ADN!9Fo0p`|v=1{k~ZENneQM0D629|v?FC!!Z ziddl3#3Yz(!dTP$W;7`qDm8+fja+WOLSC?~c-pQwIez-V-sDA&w^clA$-jOmVe*hB zP2}c%!gB7SlvL$yf*{XNEW~ASafQnCeb5+4MVn-8X8Kua+?Z)N)~+)+9;dL2E37By ztM+WL`)Gkglsj*z5dMYI^`~N|sUBXX#lC~uD!;M-=3FE0?1Ww-?=?5dS!BY&%i&@m zZCB`N8IoSKKMyrj!x40-71eRyK|!uakgMR@k34@%0}dl;fzF2-eDlZGV5)rMTXq=j z;nlgyuWPvF*JVgVKTip`&57%3V#pfnI(qvQ?4HW@$y8pnmqRglODVGTmV*4RfXNP1 zYIP^Ha#gjVV`Q0Oox(KaswdJnA#rwA7Z62fs)mpyb9EM>QJRC8?uO?5j*hg@dB#XO ziz*9NG-(u!IyM^IoKdL6+dT=I#nE|qipVyQcMShxC^|q+9qLHoQ95maZu&SZk5AeK zLV+R2wE$0S1sO0NKq4LVEs6Ew;06xaQ~Z>Va(Qm0Z*PJyd}xg2SwXk0$1ZLyyhJwe zl6`%e79i1uyEs!E9LG!~d1Ta$t-Q<)8@W2DMVQ&1^|0z?u%Qx5?$T3amWW-SFdGQS zAP469_flK-XCcXO!+}Htsg;L15VLN`xH;p%Q+21jaZO1cM8v5E7QHZZpzn+G5Qiot z%G<1GM5^?7IV-+LaQ}fJ{^*c~K&bdS%p&!CWd) z3Qbn-4j4KmZgl2a1qc+!j%9C6C2@tt(zwXwG2uZad9Aa%v{Tnbe@=EtC8UovUB`}3 z>qxZV0~XF?@fnb!NfFViGGRgve|VV$?3Rap{-h*#?ZNI3o?5S4!g&ROp`oNYXV@do ziLY|QC(0yGJ0AF^#P<^T!uW-wDRK-NejpO9E$mDndX|;LerN`quA(%fD_Bxqm$cF} z5f0^%y9E3k$;G((XUbOcIzo)bYh#n5#IfvyyhS^9uQ)JtNSdhr$j^jXEXp>L%@V}P zVlIDtJ1%lbu@%qzI3TbGuA;4(|1)nijV)kzCxCwkw zO|dR#^Tz7;?&hFJuQV{rA;|QHY!}qnYZ7Uqf6DX((|7-Rt#`ERtI#dALb@q+)-QE- zWa-1_UMH3S1dA+~`2W~p2N!ZGnY8M;BDPnHgO;%rJnrf~D$((*W^?vBvA6wI`7iZ! z(AcImvG>>2B0;k_euV?@)8Dr+y?79I($nw61cK5@J(juEE1F>VsZjR~oQ;xRti&64 zu^D+0saoT8H1!fZMVs!L`2R zO~_l&7#C`MqtE!vYq~(VxjbwB_m7k}kr_E{1(~zw4MppwouXeOERK3io%c_1bFXZ; zIq+F&8mbg{5Q|5|OlSo!s56`MT`6zw7C~ngRJYk(?1seN9&RT0bg${-h30cddV1nR z@28Lc?EOAdZ=dP77Jprh+Io0mMGq?a{gq`s=Ub6H$i1{&o0C2oe!u$thm4KAQVW_% z*)D3P%Iyu;UC(Ahpb}52Ukw$xNY2@07VkBZiF~AkS1S?RZZcZ|Or6cf3!vOB@famR z%bL{VZu}ebU8~yCE`|U$EP#EaV-Z9N#O*IvlVK|twB&HLIH|*>L!%H4?rSKvu}mT? z6en?xK^Wo+ zJaR}ggcvjPe!%#gORPy6%RpGD-YjZ}%4gq|CoCeiZ=f6>l1IAf0O4Hc*(1pif`df< zm50Mvn{@7I1g0v}qVganTQfdH03%shgFV^n+tNQJFJaB4zVh3Z2TqoM8HFiVSHKaH zW}VE`q+(12%LL5TRG2GE7}ut9LLmi85=8F+EHX(83KqxlkE}C4M$Mz?DOA!RJCJ5- zPv~*BcJu*Q z@7ZW6T?D^3trs}%7rNYo7}BH}L)0BrWKFW%QJto-g)2NSu^w)(h<|!sm;T|v^!j|W z%vWiWhVCzI7(# zolC}PqJ%aV({e}%b@6zVi<^yXo~H))b)Sa-N0Nid4`y-H#<+pdHi!+!A29*^YQ()M zcdWnqBd^(CJ6hGWd4vV0TS=*MU-EhxdR?t6Hc>>qhVnHjTiRmAx(@N|R zF(Lmkv}&en$}d{MCG5$SRahmY?0MWwf-)@r-XclO~nS?b>weiI|jCBOslVng#@`4`^=3(dv0P@fgl@g zs8hzso^-p&Q**7wW6GOf9_}^_@Xg{bKPA8FOR31n49)^9c)?V-lF=FZpwbRGhk-4c z=4csk0)G@pwh^~o+4-D@+*(rsfJ8ATW7I;yCg%ASl}L0mUkm-N)sD{|mBKq1YI zO=gb=Ne})$uKKLWF18Y~t*t&MIn-2mGUvsL0hODLZ>MA5jX`n|VR9z$0&74S-Wi!s z)HsG>3Ta@r5^rzHn=`z~xrkm^_`gMkGR1NX(YWKxqvlBz&wh(F2 zAHGi|iy1e$*e8dq6H!bNuz2)9k!Bd4weQ0;+;$5za9fLuG6(;Y=*+_y(u5^z6^MP+ zbHi%lmG3!ehe2~G10e`W=d3BMUsK?_AfvD8PRG6(+ z%a1vb<#W@3wRbBwB<3>{{fAZ$E$^G=OOCBOzVTEf6q4S4i)_Nzw1H`)AcQvX$bLr| zVp7+km|052(0~Hn&S;|yfGIrP;#3nyMkF{zzDup~6h&QT!(Y*nS!fQGhfnZFS#6`* zm-|;2D5eVv%A1~mXA4H0_O|#g98U?JNA<*~+#|`S689vTBVr1{;skf=^pLZ#><1MV z(X=qA@|a0aB~MCUh+>-V{NjG|u@3y_G{A*m>xAY(Of79(Jh8rKz2&qUIrqecd$yRD zn^=pM`$TZRk)Di<<~P0ncB_pY2G=saS=#llCu%du!J+kRWHpnNH!Xve)u1{!vc^-P z5kl7t?RxT?ti#Y=z!E#gL$(@`O@htY_=_g1u+@bS-aKQE|!TGWX|= zA#+5$a=hL>IXO2&jy}ztRZJ~Lp ziuD&}wHfZ=Z%uZ)9kaS}sn+|@l}WBHM#{-yV$;tTc*{qZ{e4l}+a2 zkx8;E@BQ*|dcXGLxwmWGgnQh8#@ggEx`T{e@!f9nh3fn>#no$0vM>jq1{Gji-80u6 z;L^+T+I+fKL&S%JRcXQnQJy9>!GxxtAJ2d5%=3s%kBYe;;l2E3>VgUH1$%$j0u#XT z^^Ew#(x&$hazn__-;LIcLG&Anp{Jb+~ulpzc zsNi{Zsa)19}UHB}z_wTAzH))upi`wQM$uJ!5KYAW#HXzA>@1rzK4y|Ob- zdiv;;$J#v_yVhLi1Fi?KPN~={!jyVPI=MVh zhs#GY+%4;=YhdbLKVb8~dg0;fr2-nO)|wx5-Kb%3+fKs#M|Sv%soR~~uEzqMzk9*4 zbStF`p3(V}x|7@mg3NE{b~vuJVHNV<3oOFEGbzVUa#iq;ZoeCI_}+rZ$k+eol%_5T zi!*<;_jjF8)%(8M3G>u%{hIe**4TUc5$Rj;qHB)K^IN+|Yf{ZVVcrz}))@wmHQ$QCXXp$C zZ&5P@8s48cMMD`V;j(}MXfKn(MlKLXfemQ5VUJL%3TR832+-a*29+>H5a$TcaN<1G a_@B8?&rY#tmCaQKAnbQjf literal 0 HcmV?d00001 diff --git a/docs/Private Kit Diagram.png b/docs/Private_Kit_Diagram.png similarity index 100% rename from docs/Private Kit Diagram.png rename to docs/Private_Kit_Diagram.png diff --git a/reset_react.sh b/reset_react.sh deleted file mode 100755 index 71aa5bc657..0000000000 --- a/reset_react.sh +++ /dev/null @@ -1,11 +0,0 @@ -rm -rf node_modules -rm package-lock.json -rm yarn.lock -rm -rf ios/Pods -rm ios/Podfile.lock - -yarn install -cd ios -pod install -cd .. - From 3d38fb6c7c9fda726845259a1cb1dbb3f9119784 Mon Sep 17 00:00:00 2001 From: Ganesh V Date: Sat, 2 May 2020 01:50:17 +0530 Subject: [PATCH 58/60] iOS: Added local notification in terminate function. (#521) Co-authored-by: Tim Stirrat --- ios/COVIDSafePaths/AppDelegate.m | 43 +++++++++++++++++++++++++------- ios/en.lproj/Localizable.strings | 2 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/ios/COVIDSafePaths/AppDelegate.m b/ios/COVIDSafePaths/AppDelegate.m index 48914b0b8d..4d17a644a0 100644 --- a/ios/COVIDSafePaths/AppDelegate.m +++ b/ios/COVIDSafePaths/AppDelegate.m @@ -77,16 +77,41 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI [RNCPushNotificationIOS didReceiveLocalNotification:notification]; } +-(BOOL) isFirstTimeClosing { + //Show local notifiation at first time only. + if(![[NSUserDefaults standardUserDefaults] boolForKey:@"cxfed_NotificationAtFirstTimeOnly"]) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"cxfed_NotificationAtFirstTimeOnly"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + return TRUE; + } + return FALSE; +} + +-(BOOL) hasNotificationPermissions { + //Checking local notification permission or not. + UIUserNotificationSettings *grantedSettings = [[UIApplication sharedApplication] currentUserNotificationSettings]; + if (grantedSettings.types != UIUserNotificationTypeNone){ + return TRUE; + } + return FALSE; +} + +-(void) notifThatWeAreStillTracking { + // Set local notification. + UILocalNotification *_localNotification = [[UILocalNotification alloc] init]; + _localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5]; + _localNotification.timeZone = [NSTimeZone defaultTimeZone]; + _localNotification.alertTitle = NSLocalizedString(@"ios.app_closed_alert_title", @"Title of notification when app is closed"); + _localNotification.alertBody = NSLocalizedString(@"ios.app_closed_alert_text", @"Body text of notification when app is closed"); + _localNotification.soundName = UILocalNotificationDefaultSoundName; + [[UIApplication sharedApplication]scheduleLocalNotification:_localNotification]; +} + - (void)applicationWillTerminate:(UIApplication *)application { - UILocalNotification *notification = [[UILocalNotification alloc] init]; - notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:0]; - notification.alertTitle = NSLocalizedString(@"ios.app_closed_alert_title", @"Title of notification when app is closed"); - notification.alertBody = NSLocalizedString(@"ios.app_closed_alert_text", @"Body text of notification when app is closed"); - notification.timeZone = [NSTimeZone defaultTimeZone]; - notification.soundName = UILocalNotificationDefaultSoundName; - notification.applicationIconBadgeNumber = 0; - - [RNCPushNotificationIOS didReceiveLocalNotification:notification]; + if([self isFirstTimeClosing] && [self hasNotificationPermissions]) { + // Show local notification at first time only when app quit. + [self notifThatWeAreStillTracking]; + } } -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler diff --git a/ios/en.lproj/Localizable.strings b/ios/en.lproj/Localizable.strings index de0916853e..a2baa4eac7 100644 --- a/ios/en.lproj/Localizable.strings +++ b/ios/en.lproj/Localizable.strings @@ -1,4 +1,4 @@ /* Body text of notification when app is closed on iOS */ -"ios.app_closed_alert_text" = "COVID Safe Paths requires to be running."; +"ios.app_closed_alert_text" = "COVID Safe Paths is securely storing your GPS coordinates once every five minutes on this device."; /* Title of notification when app is closed on iOS */ "ios.app_closed_alert_title" = "COVID Safe Paths was closed"; From 89f59b827f24ff672d59a860d2fabc9b9bf620fb Mon Sep 17 00:00:00 2001 From: Tim Stirrat Date: Sat, 2 May 2020 09:40:25 -0700 Subject: [PATCH 59/60] Add eslint react-hooks checks (#751) --- .eslintrc.js | 3 ++- app/views/ExposureHistory/ExposureHistory.js | 12 ++++++------ app/views/Settings.js | 11 +++++------ package.json | 1 + yarn.lock | 5 +++++ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c86b6f8434..5a1f5eff8d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,10 +3,11 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', + 'plugin:react-hooks/recommended', 'plugin:jest/recommended', ], parser: 'babel-eslint', - plugins: ['react', 'react-native', 'detox'], + plugins: ['react', 'react-hooks', 'react-native', 'detox'], parserOptions: { ecmaVersion: 6, sourceType: 'module', diff --git a/app/views/ExposureHistory/ExposureHistory.js b/app/views/ExposureHistory/ExposureHistory.js index 8d9ed2f8fc..cec5d8c091 100644 --- a/app/views/ExposureHistory/ExposureHistory.js +++ b/app/views/ExposureHistory/ExposureHistory.js @@ -35,18 +35,18 @@ export const ExposureHistoryScreen = ({ navigation }) => { fetchData(); + const handleBackPress = () => { + navigation.goBack(); + return true; + }; + BackHandler.addEventListener('hardwareBackPress', handleBackPress); // teardown code return () => { BackHandler.removeEventListener('hardwareBackPress', handleBackPress); }; - }, []); - - const handleBackPress = () => { - navigation.goBack(); - return true; - }; + }, [navigation]); const hasExposure = history?.length && history.some(h => h.exposureMinutes > 0); diff --git a/app/views/Settings.js b/app/views/Settings.js index bfa29be41c..ed04d1dc33 100644 --- a/app/views/Settings.js +++ b/app/views/Settings.js @@ -33,12 +33,11 @@ export const SettingsScreen = ({ navigation }) => { navigation.goBack(); }; - const handleBackPress = () => { - backToMain(); - return true; - }; - useEffect(() => { + const handleBackPress = () => { + navigation.goBack(); + return true; + }; BackHandler.addEventListener('hardwareBackPress', handleBackPress); // TODO: this should be a service or hook @@ -52,7 +51,7 @@ export const SettingsScreen = ({ navigation }) => { return () => { BackHandler.removeEventListener('hardwareBackPress', handleBackPress); }; - }, []); + }, [navigation]); const locationToggleButtonPressed = async () => { try { diff --git a/package.json b/package.json index e42c4dd9be..ffd50b7aa0 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "eslint": "^6.8.0", "eslint-plugin-detox": "^1.0.0", "eslint-plugin-jest": "^23.8.2", + "eslint-plugin-react-hooks": "^4.0.0", "husky": "^4.2.3", "i18next-parser": "tstirrat/i18next-parser#guard-plural-rule", "import-sort-config": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index 6582115f1e..48520b7e71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3596,6 +3596,11 @@ eslint-plugin-react-hooks@^2.0.1: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz#4ef5930592588ce171abeb26f400c7fbcbc23cd0" integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g== +eslint-plugin-react-hooks@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.0.tgz#81196b990043cde339e25c6662aeebe32ac52d01" + integrity sha512-YKBY+kilK5wrwIdQnCF395Ya6nDro3EAMoe+2xFkmyklyhF16fH83TrQOo9zbZIDxBsXFgBbywta/0JKRNFDkw== + eslint-plugin-react-native-globals@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" From 9bb5a5717bab37184333173682d1f4c2077740c9 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Sat, 2 May 2020 12:34:57 -0500 Subject: [PATCH 60/60] Recommend PR best practices in PR template (#750) --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6107a0b784..cab9b30998 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,6 @@ - + + +#### Description: @@ -10,6 +12,6 @@ -#### How to test +#### How to test: