From bf3cd2655f6998d1f4b7d08ae110882e7b37edbe Mon Sep 17 00:00:00 2001 From: elocin Date: Tue, 2 Apr 2024 15:23:12 +0800 Subject: [PATCH] feat(ui-ux): UI for opt out anonymized data instrumentation (#4177) * added analytics section in settings page * added ios prompt * added functionality * added DE and FR translations * added alert for first login * fixed typing issue * added missing translations * added localstorage * added e2e test * added chinese translations * added missing chinese translations * only saveTx if isAnalyticsOn is true * fixed lint issue * added boolean for first modal * changed to use local storage * cleaned up local storage * stored first modal in local storage too * revert additional space in main.tsx * update e2e test * added border * pr comments (name change and add timeout) * added 1000ms timeout for first modal * changed timeout to 5 secs * revert back to 1000ms * switched analytics switch to use state variable instead * switched to state variable for condition check --- mobile-app/App.tsx | 59 ++++++------ .../app/api/persistence/analytics_storage.ts | 21 +++++ .../OceanInterface/OceanInterface.tsx | 27 ++++-- .../screens/Portfolio/PortfolioScreen.tsx | 29 ++++++ .../screens/Settings/SettingsNavigator.tsx | 10 ++ .../screens/Settings/SettingsScreen.tsx | 10 +- .../Settings/screens/AnalyticsScreen.tsx | 93 +++++++++++++++++++ .../wallet/settings/settings.spec.ts | 21 +++++ shared/contexts/AnalyticsProvider.tsx | 87 +++++++++++++++++ shared/translations/languages/de.json | 16 +++- shared/translations/languages/es.json | 16 +++- shared/translations/languages/fr.json | 16 +++- shared/translations/languages/it.json | 16 +++- shared/translations/languages/zh-Hans.json | 16 +++- shared/translations/languages/zh-Hant.json | 16 +++- 15 files changed, 408 insertions(+), 45 deletions(-) create mode 100644 mobile-app/app/api/persistence/analytics_storage.ts create mode 100644 mobile-app/app/screens/AppNavigator/screens/Settings/screens/AnalyticsScreen.tsx create mode 100644 shared/contexts/AnalyticsProvider.tsx diff --git a/mobile-app/App.tsx b/mobile-app/App.tsx index b841265c61..b381ff27fe 100644 --- a/mobile-app/App.tsx +++ b/mobile-app/App.tsx @@ -49,6 +49,7 @@ import { FavouritePoolpairProvider } from "@contexts/FavouritePoolpairContext"; import BigNumber from "bignumber.js"; import { EVMProvider } from "@contexts/EVMProvider"; import { CustomServiceProvider } from "@contexts/CustomServiceProvider"; +import { AnalyticsProvider } from "@shared-contexts/AnalyticsProvider"; /** * Loads @@ -110,35 +111,37 @@ export default function App(): JSX.Element | null { colorScheme={colorScheme} logger={Logging} > - - - - - - - + + + + + + - - -
- - - - - - - - - + + + +
+ + + + + + + + + + diff --git a/mobile-app/app/api/persistence/analytics_storage.ts b/mobile-app/app/api/persistence/analytics_storage.ts new file mode 100644 index 0000000000..0f9964eb17 --- /dev/null +++ b/mobile-app/app/api/persistence/analytics_storage.ts @@ -0,0 +1,21 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; + +const STORAGE_PREFIX_KEY = "WALLET."; + +export async function getStorageItem(key: string): Promise { + if (typeof window === "undefined") { + return null; + } + const prefixedKey = `${STORAGE_PREFIX_KEY}${key}`; + const value = await AsyncStorage.getItem(prefixedKey); + const currentValue = JSON.parse(value || String(null)); + return currentValue === null ? undefined : currentValue; +} + +export async function setStorageItem(key: string, value: T) { + if (typeof window === "undefined") { + return; + } + const prefixedKey = `${STORAGE_PREFIX_KEY}${key}`; + await AsyncStorage.setItem(prefixedKey, JSON.stringify(value)); +} diff --git a/mobile-app/app/components/OceanInterface/OceanInterface.tsx b/mobile-app/app/components/OceanInterface/OceanInterface.tsx index 594745ac01..dcbcea21e3 100644 --- a/mobile-app/app/components/OceanInterface/OceanInterface.tsx +++ b/mobile-app/app/components/OceanInterface/OceanInterface.tsx @@ -30,6 +30,7 @@ import { import { getReleaseChannel } from "@api/releaseChannel"; import { useAppDispatch } from "@hooks/useAppDispatch"; import { useFeatureFlagContext } from "@contexts/FeatureFlagContext"; +import { useAnalytics } from "@shared-contexts/AnalyticsProvider"; import { TransactionDetail } from "./TransactionDetail"; import { TransactionError } from "./TransactionError"; @@ -121,6 +122,7 @@ export function OceanInterface(): JSX.Element | null { const { getTransactionUrl } = useDeFiScanContext(); const { network } = useNetworkContext(); const { isFeatureAvailable } = useFeatureFlagContext(); + const { isAnalyticsOn } = useAnalytics(); const isSaveTxEnabled = isFeatureAvailable("save_tx"); // store @@ -179,11 +181,19 @@ export function OceanInterface(): JSX.Element | null { calledTx !== tx?.tx.txId && // to ensure that api is only called once per tx tx?.tx.txId !== undefined && network === EnvironmentNetwork.MainNet && - isSaveTxEnabled // feature flag + isSaveTxEnabled && // feature flag + isAnalyticsOn ) { saveTx(tx.tx.txId); } - }, [tx?.tx.txId, calledTx, tx?.broadcasted, network, isSaveTxEnabled]); + }, [ + tx?.tx.txId, + calledTx, + tx?.broadcasted, + network, + isSaveTxEnabled, + isAnalyticsOn, + ]); useEffect(() => { // get evm tx id and url (if any) @@ -199,13 +209,12 @@ export function OceanInterface(): JSX.Element | null { }; if (tx !== undefined) { - const isTransferDomainTx = tx?.tx.vout.some( - (vout) => - vout.script?.stack.some( - (item: any) => - item.type === "OP_DEFI_TX" && - item.tx?.name === "OP_DEFI_TX_TRANSFER_DOMAIN", - ), + const isTransferDomainTx = tx?.tx.vout.some((vout) => + vout.script?.stack.some( + (item: any) => + item.type === "OP_DEFI_TX" && + item.tx?.name === "OP_DEFI_TX_TRANSFER_DOMAIN", + ), ); if (isTransferDomainTx) { fetchEvmTx(tx.tx.txId); diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/PortfolioScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/PortfolioScreen.tsx index cefc8676d4..315147da0c 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/PortfolioScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/PortfolioScreen.tsx @@ -73,6 +73,8 @@ import * as SplashScreen from "expo-splash-screen"; import { useLogger } from "@shared-contexts/NativeLoggingProvider"; import { bottomTabDefaultRoutes } from "@screens/AppNavigator/constants/DefaultRoutes"; import { DomainType, useDomainContext } from "@contexts/DomainContext"; +import { WalletAlert } from "@components/WalletAlert"; +import { useAnalytics } from "@shared-contexts/AnalyticsProvider"; import { AddressSelectionButtonV2 } from "./components/AddressSelectionButtonV2"; import { ActionButtons } from "./components/ActionButtons"; import { @@ -108,6 +110,7 @@ export function PortfolioScreen({ navigation }: Props): JSX.Element { const client = useWhaleApiClient(); const whaleRpcClient = useWhaleRpcClient(); const { address } = useWalletContext(); + const { hasAnalyticsModalBeenShown, setStorage } = useAnalytics(); const { denominationCurrency, setDenominationCurrency } = useDenominationCurrency(); @@ -167,6 +170,32 @@ export function PortfolioScreen({ navigation }: Props): JSX.Element { }); }, []); + useEffect(() => { + setTimeout(() => { + if (hasAnalyticsModalBeenShown === "false") { + WalletAlert({ + title: translate( + "screens/AnalyticsScreen", + "Data is now collected to improve experience.", + ), + message: translate( + "screens/AnalyticsScreen", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.", + ), + buttons: [ + { + text: translate("screens/AnalyticsScreen", "Continue"), + style: "cancel", + onPress: async () => { + setStorage("IS_ANALYTICS_MODAL_SHOWN", "true"); + }, + }, + ], + }); + } + }, 1000); + }, [hasAnalyticsModalBeenShown]); + const fetchPortfolioData = (): void => { batch(() => { dispatch( diff --git a/mobile-app/app/screens/AppNavigator/screens/Settings/SettingsNavigator.tsx b/mobile-app/app/screens/AppNavigator/screens/Settings/SettingsNavigator.tsx index 575c0eac12..9437376763 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Settings/SettingsNavigator.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Settings/SettingsNavigator.tsx @@ -13,6 +13,7 @@ import { WhitelistedAddress } from "@store/userPreferences"; import { NetworkSelectionScreen } from "@screens/AppNavigator/screens/Settings/screens/NetworkSelectionScreen"; import { CfpDfipProposalsFaq } from "@screens/AppNavigator/screens/Portfolio/screens/CfpDfipProposalsFaq"; import { DomainType } from "@contexts/DomainContext"; +import { AnalyticsScreen } from "@screens/AppNavigator/screens/Settings/screens/AnalyticsScreen"; import { AboutScreen } from "./screens/AboutScreen"; import { CommunityScreen } from "./screens/CommunityScreen"; import { LanguageSelectionScreen } from "./screens/LanguageSelectionScreen"; @@ -106,6 +107,15 @@ export function SettingsNavigator(): JSX.Element { }} /> + + language?.startsWith(languageItem.locale), + const selectedLanguage = languages.find((languageItem) => + language?.startsWith(languageItem.locale), ); const revealRecoveryWords = useCallback(() => { @@ -179,6 +179,12 @@ export function SettingsScreen({ navigation }: Props): JSX.Element { border testID="view_recovery_words" /> + navigation.navigate("AnalyticsScreen")} + /> ( + isAnalyticsOn === "true", + ); + + return ( + + + + + {translate("screens/AnalyticsScreen", "Allow data access")} + + { + if (isSwitchOn) { + WalletAlert({ + title: translate( + "screens/AnalyticsScreen", + "Are you sure you want to restrict data access?", + ), + message: translate( + "screens/AnalyticsScreen", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?", + ), + buttons: [ + { + text: translate("screens/AnalyticsScreen", "Cancel"), + style: "cancel", + }, + { + text: translate("screens/AnalyticsScreen", "Restrict data"), + onPress: async () => { + setStorage("IS_ANALYTICS_ON", "false"); + setIsSwitchOn(false); + }, + style: "destructive", + }, + ], + }); + } else { + setStorage("IS_ANALYTICS_ON", "true"); + setIsSwitchOn(true); + } + }} + value={isSwitchOn} + testID="analytics_switch" + /> + + + {translate( + "screens/AnalyticsScreen", + "Participate in BR Analytics to help us make DeFiChain Wallet better.", + )} + + + ); +} diff --git a/mobile-app/cypress/e2e/functional/wallet/settings/settings.spec.ts b/mobile-app/cypress/e2e/functional/wallet/settings/settings.spec.ts index 6b60c81fa8..def9bcd4c9 100644 --- a/mobile-app/cypress/e2e/functional/wallet/settings/settings.spec.ts +++ b/mobile-app/cypress/e2e/functional/wallet/settings/settings.spec.ts @@ -98,4 +98,25 @@ context("Wallet - Settings", { testIsolation: false }, () => { "rgb(217, 123, 1)", ); }); + + it("should activate analytics by default (localstorage)", () => { + cy.url().should("include", "app/Settings/SettingsScreen", () => { + expect(localStorage.getItem("WALLET.IS_ANALYTICS_ON")).to.eq("true"); + }); + }); + + it("should switch and pop up should up to switch off analytics (local storage to be updated)", () => { + cy.getByTestID("setting_analytics").click(); + cy.getByTestID("analytics_switch").click(); + cy.on("window:alert", (message) => { + expect(message).to.equal( + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?", + ); + cy.contains("Restrict data").click(); + }); + cy.should(() => { + const analyticsValue = localStorage.getItem("WALLET.IS_ANALYTICS_ON"); + expect(analyticsValue).to.eq('"false"'); + }); + }); }); diff --git a/shared/contexts/AnalyticsProvider.tsx b/shared/contexts/AnalyticsProvider.tsx new file mode 100644 index 0000000000..fddfff738d --- /dev/null +++ b/shared/contexts/AnalyticsProvider.tsx @@ -0,0 +1,87 @@ +import React, { + createContext, + PropsWithChildren, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { + getStorageItem, + setStorageItem, +} from "@api/persistence/analytics_storage"; + +interface AnalyticsContextType { + isAnalyticsOn?: string; + hasAnalyticsModalBeenShown?: string; + getStorage: (key: string) => string | undefined; + setStorage: (key: string, value: string) => void; +} + +const AnalyticsContext = createContext(undefined as any); + +export const useAnalytics = (): AnalyticsContextType => { + return useContext(AnalyticsContext); +}; + +export function AnalyticsProvider(props: PropsWithChildren) { + const [isAnalyticsOn, setIsAnalyticsOn] = useState("true"); + const [hasAnalyticsModalBeenShown, setHasAnalyticsModalBeenShown] = + useState("false"); + + const setAnalyticsValue = async () => { + const isAnalyticsOnKeyStorage = + await getStorageItem("IS_ANALYTICS_ON"); + if (isAnalyticsOnKeyStorage) { + setIsAnalyticsOn(isAnalyticsOnKeyStorage); + } + + const hasAnalyticsModalBeenShownKeyStorage = await getStorageItem( + "IS_ANALYTICS_MODAL_SHOWN", + ); + if (hasAnalyticsModalBeenShownKeyStorage) { + setHasAnalyticsModalBeenShown(hasAnalyticsModalBeenShownKeyStorage); + } + }; + + useEffect(() => { + setAnalyticsValue(); + }, []); + + const context: AnalyticsContextType = useMemo(() => { + const setStorage = (key: string, value: string) => { + if (key === "IS_ANALYTICS_ON") { + setIsAnalyticsOn(value); + setStorageItem(key, value); + } else if (key === "IS_ANALYTICS_MODAL_SHOWN") { + setHasAnalyticsModalBeenShown(value); + setStorageItem(key, value); + } + }; + + const getStorage = (key: string) => { + let value; + if (key === "IS_ANALYTICS_ON") { + value = isAnalyticsOn; + } else if (key === "IS_ANALYTICS_MODAL_SHOWN") { + value = hasAnalyticsModalBeenShown; + } + return value; + }; + return { + isAnalyticsOn: isAnalyticsOn === null ? undefined : isAnalyticsOn, + hasAnalyticsModalBeenShown: + hasAnalyticsModalBeenShown === null + ? undefined + : hasAnalyticsModalBeenShown, + getStorage, + setStorage, + }; + }, [hasAnalyticsModalBeenShown, isAnalyticsOn]); + + return ( + + {props.children} + + ); +} diff --git a/shared/translations/languages/de.json b/shared/translations/languages/de.json index 56809e9ae0..3597dac6f9 100644 --- a/shared/translations/languages/de.json +++ b/shared/translations/languages/de.json @@ -436,7 +436,8 @@ "View on Scan": "Ansicht auf Scan", "Go back": "Zurück", "Service Providers": "Dienstanbieter", - "Custom (3rd-party)": "Benutzerdefiniert (\"Drittanbieter\")" + "Custom (3rd-party)": "Benutzerdefiniert (\"Drittanbieter\")", + "Analytics" : "Analytik" }, "screens/AboutScreen": { "About": "Über", @@ -2333,5 +2334,18 @@ "Submitting proposal": "Proposal einreichen", "Proposal Submitted": "Proposal eingereicht", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "Die Gebühren für Proposals werden eingeführt, um verantwortungsvollere Einreichungen zu fördern.\nDie Proposal-Gebühren werden auf Grundlage der gewählten Art des Proposals und des beantragten Betrags (nur für CFPs) berechnet.\nEine genauere Aufschlüsselung findest du in unserem FAQ-Bereich." + }, + "screens/AnalyticsScreen": { + "Analytics" : "Analytik", + "ANALYTICS": "ANALYTIK", + "Allow data access": "Erlaube Datenzugriff", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "Beteilige dich an BR Analytics, um uns zu helfen, DeFiChain Wallet besser zu machen.", + "Are you sure you want to restrict data access?": "Bist du sicher, dass du den Datenzugriff einschränken willst?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "Deine Daten bleiben immer anonym und werden nur für Verbesserungen verwendet. Bist du sicher, dass du den Datenzugriff einschränken willst?", + "Restrict data": "Datenzugriff einschränken", + "Cancel": "Abbrechen", + "Continue": "Weiter", + "Data is now collected to improve experience.": "Daten werden jetzt gesammelt, um das Erlebnis zu verbessern.", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "Seit der neuesten Version sammelt diese Wallet anonymisierte, nicht identifizierbare leistungsbezogene Daten. Du kannst dich jederzeit auf der Einstellungsseite abmelden." } } diff --git a/shared/translations/languages/es.json b/shared/translations/languages/es.json index 136cacbe8c..eef335d3c5 100644 --- a/shared/translations/languages/es.json +++ b/shared/translations/languages/es.json @@ -449,7 +449,8 @@ "View on Scan": "View on Scan", "Go back": "Vuelve atrás", "Service Providers": "Service Providers", - "Custom (3rd-party)": "Custom (3rd-party)" + "Custom (3rd-party)": "Custom (3rd-party)", + "Analytics": "Analytics" }, "screens/AboutScreen": { "About": "Más información", @@ -2365,5 +2366,18 @@ "Submitting proposal": "Submitting proposal", "Proposal Submitted": "Proposal Submitted", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown." + }, + "screens/AnalyticsScreen": { + "Analytics": "Analytics", + "ANALYTICS": "ANALYTICS", + "Allow data access": "Allow data access", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "Participate in BR Analytics to help us make DeFiChain Wallet better.", + "Are you sure you want to restrict data access?": "Are you sure you want to restrict data access?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?", + "Restrict data": "Restrict data", + "Cancel": "Cancel", + "Continue": "Continua", + "Data is now collected to improve experience.": "Data is now collected to improve experience.", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page." } } diff --git a/shared/translations/languages/fr.json b/shared/translations/languages/fr.json index f82a5f4974..22255a00f2 100644 --- a/shared/translations/languages/fr.json +++ b/shared/translations/languages/fr.json @@ -446,7 +446,8 @@ "View on Scan": "Voir sur Scan", "Go back": "Retour", "Service Providers": "Fournisseurs de services", - "Custom (3rd-party)": "Personnalisé (tierce partie)" + "Custom (3rd-party)": "Personnalisé (tierce partie)", + "Analytics" : "Analyse" }, "screens/AboutScreen": { "About": "A propos de l'appli", @@ -2346,5 +2347,18 @@ "Submitting proposal": "Soumettre proposition", "Proposal Submitted": "Proposition soumise", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "Les frais de proposition sont mis en place pour encourager des soumissions plus responsables.\nLes frais de proposition sont calculés en fonction du type de proposition sélectionné, et du montant demandé (uniquement pour les CFP).\nVeuillez vous référer à notre section FAQ pour une explication plus détaillée." + }, + "screens/AnalyticsScreen": { + "Analytics" : "Analyse", + "ANALYTICS": "ANALYSE", + "Allow data access": "Autoriser l'accès aux données", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "Participez à BR Analytics pour nous aider à améliorer DeFiChain Wallet.", + "Are you sure you want to restrict data access?": "Êtes-vous sûr de vouloir restreindre l'accès aux données ?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "Vos données restent toujours anonymes et ne sont utilisées qu'à des fins d'amélioration. Êtes-vous sûr de vouloir restreindre l'accès aux données ?", + "Restrict data": "Restreindre les données", + "Cancel": "Annuler", + "Continue": "Suivant", + "Data is now collected to improve experience.": "Les données sont maintenant collectées pour améliorer l'expérience", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "À partir de la dernière version, ce portefeuille collecte désormais des données non identifiables liées à la performance. Vous pouvez choisir de vous désinscrire à tout moment à partir de la page des paramètres." } } diff --git a/shared/translations/languages/it.json b/shared/translations/languages/it.json index ea29d76a2e..393c458bf7 100644 --- a/shared/translations/languages/it.json +++ b/shared/translations/languages/it.json @@ -450,7 +450,8 @@ "View on Scan": "View on Scan", "Go back": "Torna indietro", "Service Providers": "Service Providers", - "Custom (3rd-party)": "Custom (3rd-party)" + "Custom (3rd-party)": "Custom (3rd-party)", + "Analytics": "Analytics" }, "screens/AboutScreen": { "About": "Informazioni su", @@ -2360,5 +2361,18 @@ "Submitting proposal": "Submitting proposal", "Proposal Submitted": "Proposal Submitted", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown." + }, + "screens/AnalyticsScreen": { + "Analytics": "Analytics", + "ANALYTICS": "ANALYTICS", + "Allow data access": "Allow data access", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "Participate in BR Analytics to help us make DeFiChain Wallet better.", + "Are you sure you want to restrict data access?": "Are you sure you want to restrict data access?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?", + "Restrict data": "Restrict data", + "Cancel": "Cancel", + "Continue": "Continua", + "Data is now collected to improve experience.": "Data is now collected to improve experience.", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page." } } diff --git a/shared/translations/languages/zh-Hans.json b/shared/translations/languages/zh-Hans.json index 9811c60461..a174023512 100644 --- a/shared/translations/languages/zh-Hans.json +++ b/shared/translations/languages/zh-Hans.json @@ -437,7 +437,8 @@ "View on Scan": "往 DeFi Scan 查看", "Go back": "回到上一页", "Service Providers": "服务提供商", - "Custom (3rd-party)": "自定义(第三方" + "Custom (3rd-party)": "自定义(第三方", + "Analytics": "分析" }, "screens/AboutScreen": { "About": "关于", @@ -2342,5 +2343,18 @@ "Submitting proposal": "正在提交提案", "Proposal Submitted": "提案提交完成", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "实施提案费用是为了鼓励更负责任的提案。\n提案费用是根据所选的提案类型和要求的金额进行计算的(仅适用于CFP)。\n请参阅我们的FAQ部分以获得更详细的资料。" + }, + "screens/AnalyticsScreen": { + "Analytics": "分析", + "ANALYTICS": "分析", + "Allow data access": "允许数据访问", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "参与 BR Analytics,帮助我们让 DeFiChain 钱包变得更好。", + "Are you sure you want to restrict data access?": "您确定要限制数据访问吗?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "您的数据始终保持匿名,并且仅用于改进。 您确定要进行限制吗?", + "Restrict data": "限制数据", + "Cancel": "取消", + "Continue": "继续", + "Data is now collected to improve experience.": "现在收集的数据是为了改善您的体验。", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "截至最新版本,该钱包现在正在收集不可识别有关性能的相关数据。 您可以随时从设置页面选择退出。" } } diff --git a/shared/translations/languages/zh-Hant.json b/shared/translations/languages/zh-Hant.json index 2cb6a469e6..404afec93f 100644 --- a/shared/translations/languages/zh-Hant.json +++ b/shared/translations/languages/zh-Hant.json @@ -437,7 +437,8 @@ "View on Scan": "往 DeFi Scan 查看", "Go back": "回到上一頁", "Service Providers": "服務提供者", - "Custom (3rd-party)": "自定義(第三方)" + "Custom (3rd-party)": "自定義(第三方)", + "Analytics": "分析" }, "screens/AboutScreen": { "About": "關於", @@ -2343,5 +2344,18 @@ "Submitting proposal": "正在提交提案", "Proposal Submitted": "提案提交完成", "Proposals fees are implemented to encourage more responsible submissions.\nProposal fees are calculated based on the type of proposal selected, and the requested amount (only for CFP).\nRefer to our FAQ section for more detailed breakdown.": "實施提案費用是為了鼓勵更負責任的提案。\n提案費用是根據所選的提案類型和要求的金額進行計算的(僅適用於CFP)。請參閱我們的FAQ部分以獲得更詳細的資料。" + }, + "screens/AnalyticsScreen": { + "Analytics": "分析", + "ANALYTICS": "分析", + "Allow data access": "允許資料存取", + "Participate in BR Analytics to help us make DeFiChain Wallet better." : "參與 BR Analytics,幫助我們讓 DeFiChain 錢包變得更好。", + "Are you sure you want to restrict data access?": "您確定要限制資料存取嗎?", + "Your data is always kept anonymous and is used only for improvements. Are you sure you want to restrict?": "您的資料始終保持匿名,並且僅用於改進。 您確定要進行限制嗎?", + "Restrict data": "限制數據", + "Cancel": "取消", + "Continue": "繼續", + "Data is now collected to improve experience.": "現在收集的資料是為了改善您的體驗。", + "As of the latest version, this wallet is now collecting non-identifiable performance-related data. You can choose to opt-out anytime from the settings page.": "截至最新版本,該錢包現在正在收集不可識別有關性能的相關數據。 您可以隨時從設定頁面選擇退出。" } }