@redacto.io/consent-sdk-react 10.0.0-beta.0 → 10.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.turbo/turbo-build.log +22 -25
  2. package/CHANGELOG.md +14 -0
  3. package/dist/index.d.mts +13 -6
  4. package/dist/index.d.ts +13 -6
  5. package/dist/index.js +6 -8
  6. package/dist/index.mjs +6 -8
  7. package/dist/privacy-center.d.mts +60 -27
  8. package/dist/privacy-center.d.ts +60 -27
  9. package/dist/privacy-center.js +1872 -1363
  10. package/dist/privacy-center.mjs +1796 -1282
  11. package/package.json +1 -1
  12. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +1 -0
  13. package/src/RedactoNoticeConsent/api/index.ts +4 -4
  14. package/src/RedactoNoticeConsent/api/types.ts +3 -2
  15. package/src/RedactoNoticeConsent/types.ts +1 -1
  16. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +1 -0
  17. package/src/RedactoNoticeConsentInline/api/index.ts +4 -4
  18. package/src/RedactoNoticeConsentInline/api/types.ts +2 -1
  19. package/src/RedactoNoticeConsentInline/types.ts +1 -1
  20. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +8 -5
  21. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +1 -1
  22. package/src/RedactoPrivacyCenter/api/actions.ts +10 -4
  23. package/src/RedactoPrivacyCenter/api/client.ts +101 -21
  24. package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +64 -35
  25. package/src/RedactoPrivacyCenter/components/Form.tsx +7 -3
  26. package/src/RedactoPrivacyCenter/components/SelectUser.tsx +83 -0
  27. package/src/RedactoPrivacyCenter/components/SelectUserGate.tsx +193 -0
  28. package/src/RedactoPrivacyCenter/components/constant.ts +11 -0
  29. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +1 -1
  30. package/src/RedactoPrivacyCenter/context/ProfileSwitchContext.tsx +15 -0
  31. package/src/RedactoPrivacyCenter/context/types.ts +1 -1
  32. package/src/RedactoPrivacyCenter/lib/types.ts +54 -2
  33. package/src/RedactoPrivacyCenter/locales/as/translation.json +7 -1
  34. package/src/RedactoPrivacyCenter/locales/bho/translation.json +7 -1
  35. package/src/RedactoPrivacyCenter/locales/bn/translation.json +7 -1
  36. package/src/RedactoPrivacyCenter/locales/brx/translation.json +7 -1
  37. package/src/RedactoPrivacyCenter/locales/doi/translation.json +7 -1
  38. package/src/RedactoPrivacyCenter/locales/en/translation.json +7 -1
  39. package/src/RedactoPrivacyCenter/locales/gom/translation.json +7 -1
  40. package/src/RedactoPrivacyCenter/locales/gu/translation.json +7 -1
  41. package/src/RedactoPrivacyCenter/locales/hi/translation.json +7 -1
  42. package/src/RedactoPrivacyCenter/locales/kn/translation.json +7 -1
  43. package/src/RedactoPrivacyCenter/locales/ks/translation.json +7 -1
  44. package/src/RedactoPrivacyCenter/locales/mai/translation.json +7 -1
  45. package/src/RedactoPrivacyCenter/locales/ml/translation.json +7 -1
  46. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +7 -1
  47. package/src/RedactoPrivacyCenter/locales/mr/translation.json +7 -1
  48. package/src/RedactoPrivacyCenter/locales/ne/translation.json +7 -1
  49. package/src/RedactoPrivacyCenter/locales/or/translation.json +7 -1
  50. package/src/RedactoPrivacyCenter/locales/pa/translation.json +7 -1
  51. package/src/RedactoPrivacyCenter/locales/sa/translation.json +7 -1
  52. package/src/RedactoPrivacyCenter/locales/sat/translation.json +7 -1
  53. package/src/RedactoPrivacyCenter/locales/sd/translation.json +7 -1
  54. package/src/RedactoPrivacyCenter/locales/ta/translation.json +7 -1
  55. package/src/RedactoPrivacyCenter/locales/te/translation.json +7 -1
  56. package/src/RedactoPrivacyCenter/locales/ur/translation.json +7 -1
  57. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +105 -0
  58. package/tests/Form.test.tsx +53 -7
  59. package/tests/consentClient.test.ts +171 -0
  60. package/tests/manageConsentServer.test.ts +38 -0
@@ -0,0 +1,193 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useState,
6
+ type ReactNode,
7
+ } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { useOrganization } from '../context/OrganizationContext';
10
+ import { useBaseUrl } from '../context/BaseUrlContext';
11
+ import { useAuth } from '../context/AuthContext';
12
+ import { ProfileSwitchProvider } from '../context/ProfileSwitchContext';
13
+ import { PCLoader } from '../ui';
14
+ import { decodeJwtToken } from '../lib/utils';
15
+ import type {
16
+ GatePhase,
17
+ IdentityCandidate,
18
+ SelectUserGateProps,
19
+ } from '../lib/types';
20
+ import { PICKED_ORG_USER_KEY } from './constant';
21
+ import { SelectUser } from './SelectUser';
22
+
23
+ /**
24
+ * Shared-contact profile gate. On mount it asks the backend which principals
25
+ * share the signed-in contact; when more than one exists (and the session isn't
26
+ * already pinned to a chosen one), it renders the "choose your profile" picker
27
+ * instead of the consent view. Picking re-scopes the session via updateTokens,
28
+ * then renders children. It sits OUTSIDE DataAvailabilityProvider so no consent
29
+ * fetch runs against an unscoped token, and degrades to children on any error.
30
+ *
31
+ * Once resolved it also exposes `switchProfile` via ProfileSwitchContext, so a
32
+ * signed-in shared contact can re-open the picker and move to another profile.
33
+ */
34
+ export const SelectUserGate = ({ children }: SelectUserGateProps) => {
35
+ const { t } = useTranslation();
36
+ const { organization } = useOrganization();
37
+ const organizationId = organization?.uuid || '';
38
+ const workspaceId = organization?.workspaces?.[0]?.uuid || '';
39
+ const { consentClient } = useBaseUrl();
40
+ const { accessToken, updateTokens } = useAuth();
41
+
42
+ const [phase, setPhase] = useState<GatePhase>('checking');
43
+ const [candidates, setCandidates] = useState<IdentityCandidate[]>([]);
44
+ const [busyOrgUserId, setBusyOrgUserId] = useState<string | null>(null);
45
+ const [selectError, setSelectError] = useState<string | null>(null);
46
+
47
+ // The token's principal. Routine refreshes (checkAndRefreshAuth,
48
+ // refreshOtpToken) hand back a fresh token string for the SAME principal;
49
+ // keying the gate check off this decoded id rather than the raw token keeps
50
+ // those refreshes from re-running the check and flashing the picker/spinner.
51
+ const tokenOrgUserId =
52
+ (decodeJwtToken(accessToken)?.user_data?.org_user_id as string) || '';
53
+
54
+ useEffect(() => {
55
+ let cancelled = false;
56
+
57
+ if (!organizationId || !workspaceId || !accessToken) {
58
+ setPhase('resolved');
59
+ return;
60
+ }
61
+
62
+ // Already pinned to a chosen principal this session? Pass straight through
63
+ // to children — but STILL resolve the sibling list in the background so the
64
+ // user menu can offer "switch profile". The default/unscoped token has
65
+ // org_user_id = the contact and no stored pick, so the picker still shows.
66
+ const picked =
67
+ typeof window !== 'undefined'
68
+ ? sessionStorage.getItem(PICKED_ORG_USER_KEY)
69
+ : null;
70
+ const isPinned = Boolean(picked) && picked === tokenOrgUserId;
71
+
72
+ // Only block on the spinner when the picker might actually open; a pinned
73
+ // session renders children immediately and resolves siblings quietly.
74
+ setPhase(isPinned ? 'resolved' : 'checking');
75
+ (async () => {
76
+ try {
77
+ const res = await consentClient.listIdentities({
78
+ organisationUuid: organizationId,
79
+ workspaceUuid: workspaceId,
80
+ authToken: accessToken,
81
+ });
82
+ if (cancelled) {
83
+ return;
84
+ }
85
+ const list = res?.identities ?? [];
86
+ setCandidates(list);
87
+ if (list.length > 1 && !isPinned) {
88
+ setPhase('picking');
89
+ } else {
90
+ setPhase('resolved');
91
+ }
92
+ } catch {
93
+ // Ledger/endpoint unavailable — never block the Privacy Center.
94
+ if (!cancelled) {
95
+ setCandidates([]);
96
+ setPhase('resolved');
97
+ }
98
+ }
99
+ })();
100
+
101
+ return () => {
102
+ cancelled = true;
103
+ };
104
+ // consentClient is a stable factory result; re-running on its identity would
105
+ // risk a loop. Re-evaluate only when the tenant or the token's principal
106
+ // changes — not on every raw-token refresh. accessToken is read inside for
107
+ // the fetch but intentionally omitted so same-principal refreshes are no-ops.
108
+ // eslint-disable-next-line react-hooks/exhaustive-deps
109
+ }, [organizationId, workspaceId, tokenOrgUserId]);
110
+
111
+ const handleSelect = async (principalUuid: string) => {
112
+ if (!principalUuid || busyOrgUserId) {
113
+ return;
114
+ }
115
+ setBusyOrgUserId(principalUuid);
116
+ setSelectError(null);
117
+ try {
118
+ const res = await consentClient.selectPrincipal(
119
+ {
120
+ organisationUuid: organizationId,
121
+ workspaceUuid: workspaceId,
122
+ authToken: accessToken,
123
+ },
124
+ principalUuid,
125
+ );
126
+ if (!res?.token || !res?.refresh_token) {
127
+ throw new Error('select-principal returned no tokens');
128
+ }
129
+ // The picker selects by opaque uuid, but the "already pinned" check
130
+ // compares against the token's own org_user_id. Derive the pin from the
131
+ // NEW token so it matches on later reloads — it's the signed-in
132
+ // principal's own id, not a co-principal's, so nothing leaks.
133
+ if (typeof window !== 'undefined') {
134
+ const pinnedOrgUserId = decodeJwtToken(res.token)?.user_data
135
+ ?.org_user_id as string | undefined;
136
+ if (pinnedOrgUserId) {
137
+ sessionStorage.setItem(PICKED_ORG_USER_KEY, pinnedOrgUserId);
138
+ }
139
+ }
140
+ // Resolve before swapping the token so the picker doesn't flash back.
141
+ setPhase('resolved');
142
+ updateTokens(res.token, res.refresh_token);
143
+ } catch {
144
+ setBusyOrgUserId(null);
145
+ setSelectError(t('couldNotSwitchProfile'));
146
+ }
147
+ };
148
+
149
+ // Re-open the picker so a shared contact can move to another of its profiles.
150
+ // Drops the session pin and shows the picker with the already-loaded
151
+ // candidates; picking re-scopes the token exactly like the first choice.
152
+ const switchProfile = useCallback(() => {
153
+ if (typeof window !== 'undefined') {
154
+ sessionStorage.removeItem(PICKED_ORG_USER_KEY);
155
+ }
156
+ setSelectError(null);
157
+ setBusyOrgUserId(null);
158
+ setPhase('picking');
159
+ }, []);
160
+
161
+ const switchValue = useMemo(
162
+ () => ({ canSwitch: candidates.length > 1, switchProfile }),
163
+ [candidates.length, switchProfile],
164
+ );
165
+
166
+ let inner: ReactNode;
167
+ if (phase === 'checking') {
168
+ inner = (
169
+ <div className="pc-select-user-loading">
170
+ <PCLoader variant="spinner" color="info" size="lg" />
171
+ </div>
172
+ );
173
+ } else if (phase === 'picking') {
174
+ const decoded = decodeJwtToken(accessToken);
175
+ const email =
176
+ (decoded?.user_data?.primary_email as string) ||
177
+ (decoded?.primary_email as string) ||
178
+ null;
179
+ inner = (
180
+ <SelectUser
181
+ email={email}
182
+ candidates={candidates}
183
+ onSelect={handleSelect}
184
+ busyOrgUserId={busyOrgUserId}
185
+ error={selectError}
186
+ />
187
+ );
188
+ } else {
189
+ inner = <>{children}</>;
190
+ }
191
+
192
+ return <ProfileSwitchProvider value={switchValue}>{inner}</ProfileSwitchProvider>;
193
+ };
@@ -39,3 +39,14 @@ export const MARKER_TINT: Record<BadgeVariant, MarkerTint> = {
39
39
  // Internal error thrown when an activity page fails to load (surfaced to the
40
40
  // user via the i18n `failedToFetchActivityData` toast).
41
41
  export const ACTIVITY_FETCH_ERROR_MESSAGE = 'Failed to fetch activity data';
42
+
43
+ // ─── Shared-contact profile picker ───────────────────────────────────────────
44
+
45
+ // sessionStorage key pinning the principal the user chose this session, so
46
+ // routine token refreshes skip the picker.
47
+ export const PICKED_ORG_USER_KEY = 'pc_picked_org_user_id';
48
+
49
+ // The shared-contact picker/switch user-facing strings are localized via i18n —
50
+ // see the chooseYourProfile / emailLinkedToMultipleProfiles /
51
+ // contactLinkedToMultipleProfiles / signingInEllipsis / couldNotSwitchProfile /
52
+ // switchProfile keys in locales/*/translation.json.
@@ -13,7 +13,7 @@ export const BaseUrlProvider = ({
13
13
  }: {
14
14
  children: ReactNode;
15
15
  baseUrl: string; // Consent server base URL
16
- ledgerBaseUrl: string; // Go ledger base URL — serves user-consents
16
+ ledgerBaseUrl?: string; // Go ledger base URL — serves user-consents
17
17
  }) => {
18
18
  const consentClient = useMemo(
19
19
  () => createConsentClient(baseUrl, ledgerBaseUrl),
@@ -0,0 +1,15 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { ProfileSwitchValue } from '../lib/types';
3
+
4
+ // Lets any descendant of SelectUserGate (e.g. the user menu) re-open the
5
+ // shared-contact profile picker. Defaults to a no-op so consumers rendered
6
+ // outside the gate degrade gracefully.
7
+ const ProfileSwitchContext = createContext<ProfileSwitchValue>({
8
+ canSwitch: false,
9
+ switchProfile: () => {},
10
+ });
11
+
12
+ export const ProfileSwitchProvider = ProfileSwitchContext.Provider;
13
+
14
+ export const useProfileSwitch = (): ProfileSwitchValue =>
15
+ useContext(ProfileSwitchContext);
@@ -14,6 +14,6 @@ export type AuthContextValue = Readonly<{
14
14
 
15
15
  export type BaseUrlContextValue = Readonly<{
16
16
  baseUrl: string;
17
- ledgerBaseUrl: string;
17
+ ledgerBaseUrl?: string;
18
18
  consentClient: ReturnType<typeof createConsentClient>;
19
19
  }>;
@@ -349,6 +349,12 @@ export type ManageConsentResponse = {
349
349
  };
350
350
  };
351
351
 
352
+ // Unwrapped `detail` of the legacy Python manage-consent response; the Go
353
+ // ledger returns a ConsentEventResponse with no `success` flag instead.
354
+ export type LegacyManageConsentResponse = {
355
+ success: boolean;
356
+ };
357
+
352
358
  export type ManageConsentParams = {
353
359
  organizationId: string;
354
360
  workspaceId: string;
@@ -411,8 +417,10 @@ export type Receipt = {
411
417
 
412
418
  // Mirrors the Go ledger's `ConsentReceiptListResponse` (offset-paginated page
413
419
  // of the principal's receipts): flat `total`/`skip`/`limit`, not a nested
414
- // `pagination` object. `normalizeReceipts` in ReceiptsList tolerates either
415
- // envelope, but this type documents the ledger contract the SDK now calls.
420
+ // `pagination` object. Without a configured ledgerBaseUrl the legacy Python
421
+ // list is called instead, which returns a `{ data, pagination }` envelope —
422
+ // `normalizeReceipts` in ReceiptsList tolerates either shape; this type
423
+ // documents the ledger contract.
416
424
  export type ReceiptListResponse = {
417
425
  data: Receipt[];
418
426
  total: number;
@@ -814,3 +822,47 @@ export type NotificationRefreshButtonProps = Readonly<{
814
822
  onRefresh: () => void;
815
823
  iconSize?: number;
816
824
  }>;
825
+
826
+ export type IdentityCandidate = Readonly<{
827
+ uuid: string;
828
+ org_user_id?: string | null;
829
+ primary_email?: string | null;
830
+ primary_mobile?: string | null;
831
+ created_at?: string | null;
832
+ }>;
833
+
834
+ export type IdentityListResponse = Readonly<{
835
+ identities: IdentityCandidate[];
836
+ }>;
837
+
838
+ export type SelectPrincipalResponse = Readonly<{
839
+ token: string;
840
+ refresh_token: string;
841
+ expires_at?: string;
842
+ }>;
843
+
844
+ // Discrete phases of the shared-contact profile gate: `checking` probes the
845
+ // backend for sibling principals, `picking` shows the profile picker, and
846
+ // `resolved` passes through to children.
847
+ export type GatePhase = 'checking' | 'picking' | 'resolved';
848
+
849
+ export type SelectUserProps = Readonly<{
850
+ email: string | null;
851
+ candidates: IdentityCandidate[];
852
+ onSelect: (orgUserId: string) => void;
853
+ busyOrgUserId: string | null;
854
+ error?: string | null;
855
+ }>;
856
+
857
+ export type SelectUserGateProps = Readonly<{
858
+ children: React.ReactNode;
859
+ }>;
860
+
861
+ // Exposed by SelectUserGate so a signed-in session can re-open the profile
862
+ // picker (shared contact). `canSwitch` is true only when the contact maps to
863
+ // more than one principal; `switchProfile` drops the pinned choice and shows
864
+ // the picker again.
865
+ export type ProfileSwitchValue = Readonly<{
866
+ canSwitch: boolean;
867
+ switchProfile: () => void;
868
+ }>;
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "প্ৰ'ফাইল সলনি কৰক",
335
+ "chooseYourProfile": "আপোনাৰ প্ৰ'ফাইল বাছনি কৰক",
336
+ "emailLinkedToMultipleProfiles": "{{email}} একাধিক প্ৰ'ফাইলৰ সৈতে সংযুক্ত। আপুনি চাব বিচৰাটো বাছনি কৰক।",
337
+ "contactLinkedToMultipleProfiles": "এই যোগাযোগটো একাধিক প্ৰ'ফাইলৰ সৈতে সংযুক্ত। আপুনি চাব বিচৰাটো বাছনি কৰক।",
338
+ "signingInEllipsis": "ছাইন ইন হৈ আছে…",
339
+ "couldNotSwitchProfile": "সেই প্ৰ'ফাইললৈ সলনি কৰিব পৰা নগ'ল। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल बदलीं",
335
+ "chooseYourProfile": "आपन प्रोफाइल चुनीं",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एक से ढेर प्रोफाइल से जुड़ल बा। जवन रउआ देखल चाहत बानी ओकरा के चुनीं।",
337
+ "contactLinkedToMultipleProfiles": "ई संपर्क एक से ढेर प्रोफाइल से जुड़ल बा। जवन रउआ देखल चाहत बानी ओकरा के चुनीं।",
338
+ "signingInEllipsis": "साइन इन हो रहल बा…",
339
+ "couldNotSwitchProfile": "ओह प्रोफाइल पर स्विच ना हो सकल। कृपया फेर से कोसिस करीं।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "প্রোফাইল পরিবর্তন করুন",
335
+ "chooseYourProfile": "আপনার প্রোফাইল বেছে নিন",
336
+ "emailLinkedToMultipleProfiles": "{{email}} একাধিক প্রোফাইলের সাথে যুক্ত। আপনি যেটি দেখতে চান সেটি বেছে নিন।",
337
+ "contactLinkedToMultipleProfiles": "এই যোগাযোগটি একাধিক প্রোফাইলের সাথে যুক্ত। আপনি যেটি দেখতে চান সেটি বেছে নিন।",
338
+ "signingInEllipsis": "সাইন ইন হচ্ছে…",
339
+ "couldNotSwitchProfile": "সেই প্রোফাইলে স্যুইচ করা যায়নি। অনুগ্রহ করে আবার চেষ্টা করুন।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल सोलाय",
335
+ "chooseYourProfile": "नोंथांनि प्रफाइल सायख",
336
+ "emailLinkedToMultipleProfiles": "{{email}} आ मोनसेनिख्रुइ बांसिन प्रफाइलजों नांजाब दं। नोंथाङा नायनो लुबैयोब्ला बेखौ सायख।",
337
+ "contactLinkedToMultipleProfiles": "बे सोमोन्दोआ मोनसेनिख्रुइ बांसिन प्रफाइलजों नांजाब दं। नोंथाङा नायनो लुबैयोब्ला बेखौ सायख।",
338
+ "signingInEllipsis": "साइन इन जायगासिनो दं…",
339
+ "couldNotSwitchProfile": "बै प्रफाइलआव स्विच खालामनो हायाखै। अननानै फिन नाजा।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल बदलो",
335
+ "chooseYourProfile": "अपनी प्रोफाइल चुनो",
336
+ "emailLinkedToMultipleProfiles": "{{email}} इक कोला मता प्रोफाइलें कन्नै जुड़्या दा ऐ। जेह्ड़ी तुसें दिक्खनी ऐ ओह् चुनो।",
337
+ "contactLinkedToMultipleProfiles": "एह् संपर्क इक कोला मता प्रोफाइलें कन्नै जुड़्या दा ऐ। जेह्ड़ी तुसें दिक्खनी ऐ ओह् चुनो।",
338
+ "signingInEllipsis": "साइन इन होआ करदा ऐ…",
339
+ "couldNotSwitchProfile": "ओह् प्रोफाइल पर स्विच नेईं होई सकेआ। कृपा करियै दुबारा कोशिश करो।"
334
340
  }
@@ -344,5 +344,11 @@
344
344
  "forProduct": "For {{productName}}",
345
345
  "actingOnBehalfOfShort": "acting on behalf of {{nominator}}",
346
346
  "nominationChipSuffix": "· {{nominator}}",
347
- "selectPurposesForThisProduct": "Select purposes within this product."
347
+ "selectPurposesForThisProduct": "Select purposes within this product.",
348
+ "switchProfile": "Switch profile",
349
+ "chooseYourProfile": "Choose your profile",
350
+ "emailLinkedToMultipleProfiles": "{{email}} is linked to more than one profile. Choose the one you want to view.",
351
+ "contactLinkedToMultipleProfiles": "This contact is linked to more than one profile. Choose the one you want to view.",
352
+ "signingInEllipsis": "Signing in…",
353
+ "couldNotSwitchProfile": "Could not switch to that profile. Please try again."
348
354
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफायल बदला",
335
+ "chooseYourProfile": "तुमचें प्रोफायल निवडात",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एकापरस चड प्रोफायलांक जोडलां. तुमकां जें पळोवपाक जाय तें निवडात.",
337
+ "contactLinkedToMultipleProfiles": "हो संपर्क एकापरस चड प्रोफायलांक जोडला. तुमकां जें पळोवपाक जाय तें निवडात.",
338
+ "signingInEllipsis": "साइन इन जावन आसा…",
339
+ "couldNotSwitchProfile": "त्या प्रोफायलाचेर स्विच करूंक जालें ना. उपकार करून परत यत्न करात."
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "પ્રોફાઇલ બદલો",
335
+ "chooseYourProfile": "તમારી પ્રોફાઇલ પસંદ કરો",
336
+ "emailLinkedToMultipleProfiles": "{{email}} એક કરતાં વધુ પ્રોફાઇલ સાથે જોડાયેલ છે. તમે જે જોવા માંગો છો તે પસંદ કરો.",
337
+ "contactLinkedToMultipleProfiles": "આ સંપર્ક એક કરતાં વધુ પ્રોફાઇલ સાથે જોડાયેલ છે. તમે જે જોવા માંગો છો તે પસંદ કરો.",
338
+ "signingInEllipsis": "સાઇન ઇન થઈ રહ્યું છે…",
339
+ "couldNotSwitchProfile": "તે પ્રોફાઇલ પર સ્વિચ કરી શકાયું નહીં. કૃપા કરીને ફરી પ્રયાસ કરો."
334
340
  }
@@ -333,5 +333,11 @@
333
333
  "casesCreatedCount_other": "{{count}} cases created",
334
334
  "someRequestsFailed": "Some requests failed",
335
335
  "retry": "Retry",
336
- "retrying": "Retrying..."
336
+ "retrying": "Retrying...",
337
+ "switchProfile": "प्रोफ़ाइल बदलें",
338
+ "chooseYourProfile": "अपनी प्रोफ़ाइल चुनें",
339
+ "emailLinkedToMultipleProfiles": "{{email}} एक से अधिक प्रोफ़ाइल से जुड़ा है। जिसे आप देखना चाहते हैं उसे चुनें।",
340
+ "contactLinkedToMultipleProfiles": "यह संपर्क एक से अधिक प्रोफ़ाइल से जुड़ा है। जिसे आप देखना चाहते हैं उसे चुनें।",
341
+ "signingInEllipsis": "साइन इन हो रहा है…",
342
+ "couldNotSwitchProfile": "उस प्रोफ़ाइल पर स्विच नहीं किया जा सका। कृपया पुनः प्रयास करें।"
337
343
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "ಪ್ರೊಫೈಲ್ ಬದಲಾಯಿಸಿ",
335
+ "chooseYourProfile": "ನಿಮ್ಮ ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆಮಾಡಿ",
336
+ "emailLinkedToMultipleProfiles": "{{email}} ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚು ಪ್ರೊಫೈಲ್‌ಗಳಿಗೆ ಲಿಂಕ್ ಆಗಿದೆ. ನೀವು ವೀಕ್ಷಿಸಲು ಬಯಸುವುದನ್ನು ಆಯ್ಕೆಮಾಡಿ.",
337
+ "contactLinkedToMultipleProfiles": "ಈ ಸಂಪರ್ಕವು ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚು ಪ್ರೊಫೈಲ್‌ಗಳಿಗೆ ಲಿಂಕ್ ಆಗಿದೆ. ನೀವು ವೀಕ್ಷಿಸಲು ಬಯಸುವುದನ್ನು ಆಯ್ಕೆಮಾಡಿ.",
338
+ "signingInEllipsis": "ಸೈನ್ ಇನ್ ಆಗುತ್ತಿದೆ…",
339
+ "couldNotSwitchProfile": "ಆ ಪ್ರೊಫೈಲ್‌ಗೆ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "پروفائل بدلاو",
335
+ "chooseYourProfile": "پننہٕ پروفائل ژارٕو",
336
+ "emailLinkedToMultipleProfiles": "{{email}} چھُ اکہ کھۄتہ زیادٕ پروفائلن سٟتؠ جوڑنہ آمُت۔ یُس توہہِ وُچھُن چھِو یژھان سُ ژارٕو۔",
337
+ "contactLinkedToMultipleProfiles": "یہ رابطہ چھُ اکہ کھۄتہ زیادٕ پروفائلن سٟتؠ جوڑنہ آمُت۔ یُس توہہِ وُچھُن چھِو یژھان سُ ژارٕو۔",
338
+ "signingInEllipsis": "سائن اِن گژھان چھُ…",
339
+ "couldNotSwitchProfile": "تہِ پروفائلس منٛز سوِچ کرُن آو نہ۔ مہربانی کرِتھ دوبارٕ کوشش کریو۔"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल बदलू",
335
+ "chooseYourProfile": "अपन प्रोफाइल चुनू",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एकाधिक प्रोफाइलसँ जुड़ल अछि। जे अहाँ देखय चाहैत छी ओकरा चुनू।",
337
+ "contactLinkedToMultipleProfiles": "ई संपर्क एकाधिक प्रोफाइलसँ जुड़ल अछि। जे अहाँ देखय चाहैत छी ओकरा चुनू।",
338
+ "signingInEllipsis": "साइन इन भ' रहल अछि…",
339
+ "couldNotSwitchProfile": "ओ प्रोफाइल पर स्विच नहि भ' सकल। कृपया फेर सँ प्रयास करू।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "പ്രൊഫൈൽ മാറ്റുക",
335
+ "chooseYourProfile": "നിങ്ങളുടെ പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക",
336
+ "emailLinkedToMultipleProfiles": "{{email}} ഒന്നിലധികം പ്രൊഫൈലുകളുമായി ബന്ധിപ്പിച്ചിരിക്കുന്നു. നിങ്ങൾ കാണാൻ ആഗ്രഹിക്കുന്നത് തിരഞ്ഞെടുക്കുക.",
337
+ "contactLinkedToMultipleProfiles": "ഈ കോൺടാക്റ്റ് ഒന്നിലധികം പ്രൊഫൈലുകളുമായി ബന്ധിപ്പിച്ചിരിക്കുന്നു. നിങ്ങൾ കാണാൻ ആഗ്രഹിക്കുന്നത് തിരഞ്ഞെടുക്കുക.",
338
+ "signingInEllipsis": "സൈൻ ഇൻ ചെയ്യുന്നു…",
339
+ "couldNotSwitchProfile": "ആ പ്രൊഫൈലിലേക്ക് മാറാൻ കഴിഞ്ഞില്ല. വീണ്ടും ശ്രമിക്കുക."
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "প্রোফাইল হোংদোক-উ",
335
+ "chooseYourProfile": "নহাক্কী প্রোফাইল খনবিয়ু",
336
+ "emailLinkedToMultipleProfiles": "{{email}} অসি প্রোফাইল অমদগী হেন্না লোইননবা লৈ। নহাক্না য়েংনিংবা অদু খনবিয়ু।",
337
+ "contactLinkedToMultipleProfiles": "মসি কন্টেক্ট অসি প্রোফাইল অমদগী হেন্না লোইননবা লৈ। নহাক্না য়েংনিংবা অদু খনবিয়ু।",
338
+ "signingInEllipsis": "সাইন ইন তৌরি…",
339
+ "couldNotSwitchProfile": "প্রোফাইল অদুদা হোংদোকপা ঙমদ্রে। চানবীদুনা অমুক হন্না হোৎনবিয়ু।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल बदला",
335
+ "chooseYourProfile": "तुमची प्रोफाइल निवडा",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एकापेक्षा जास्त प्रोफाइलशी जोडलेला आहे. तुम्हाला जी पाहायची आहे ती निवडा.",
337
+ "contactLinkedToMultipleProfiles": "हा संपर्क एकापेक्षा जास्त प्रोफाइलशी जोडलेला आहे. तुम्हाला जी पाहायची आहे ती निवडा.",
338
+ "signingInEllipsis": "साइन इन करत आहे…",
339
+ "couldNotSwitchProfile": "त्या प्रोफाइलवर स्विच करता आले नाही. कृपया पुन्हा प्रयत्न करा."
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफाइल बदल्नुहोस्",
335
+ "chooseYourProfile": "आफ्नो प्रोफाइल छान्नुहोस्",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एकभन्दा बढी प्रोफाइलसँग जोडिएको छ। तपाईंले हेर्न चाहनुभएको छान्नुहोस्।",
337
+ "contactLinkedToMultipleProfiles": "यो सम्पर्क एकभन्दा बढी प्रोफाइलसँग जोडिएको छ। तपाईंले हेर्न चाहनुभएको छान्नुहोस्।",
338
+ "signingInEllipsis": "साइन इन हुँदैछ…",
339
+ "couldNotSwitchProfile": "त्यो प्रोफाइलमा स्विच गर्न सकिएन। कृपया फेरि प्रयास गर्नुहोस्।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "ପ୍ରୋଫାଇଲ୍ ବଦଳାନ୍ତୁ",
335
+ "chooseYourProfile": "ଆପଣଙ୍କ ପ୍ରୋଫାଇଲ୍ ବାଛନ୍ତୁ",
336
+ "emailLinkedToMultipleProfiles": "{{email}} ଏକାଧିକ ପ୍ରୋଫାଇଲ୍ ସହିତ ଯୋଡ଼ାଯାଇଛି। ଆପଣ ଦେଖିବାକୁ ଚାହୁଁଥିବାଟି ବାଛନ୍ତୁ।",
337
+ "contactLinkedToMultipleProfiles": "ଏହି ଯୋଗାଯୋଗ ଏକାଧିକ ପ୍ରୋଫାଇଲ୍ ସହିତ ଯୋଡ଼ାଯାଇଛି। ଆପଣ ଦେଖିବାକୁ ଚାହୁଁଥିବାଟି ବାଛନ୍ତୁ।",
338
+ "signingInEllipsis": "ସାଇନ୍ ଇନ୍ ହେଉଛି…",
339
+ "couldNotSwitchProfile": "ସେହି ପ୍ରୋଫାଇଲକୁ ସ୍ୱିଚ୍ କରାଯାଇପାରିଲା ନାହିଁ। ଦୟାକରି ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "ਪ੍ਰੋਫਾਈਲ ਬਦਲੋ",
335
+ "chooseYourProfile": "ਆਪਣੀ ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ",
336
+ "emailLinkedToMultipleProfiles": "{{email}} ਇੱਕ ਤੋਂ ਵੱਧ ਪ੍ਰੋਫਾਈਲਾਂ ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ। ਜੋ ਤੁਸੀਂ ਵੇਖਣਾ ਚਾਹੁੰਦੇ ਹੋ ਉਹ ਚੁਣੋ।",
337
+ "contactLinkedToMultipleProfiles": "ਇਹ ਸੰਪਰਕ ਇੱਕ ਤੋਂ ਵੱਧ ਪ੍ਰੋਫਾਈਲਾਂ ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ। ਜੋ ਤੁਸੀਂ ਵੇਖਣਾ ਚਾਹੁੰਦੇ ਹੋ ਉਹ ਚੁਣੋ।",
338
+ "signingInEllipsis": "ਸਾਈਨ ਇਨ ਹੋ ਰਿਹਾ ਹੈ…",
339
+ "couldNotSwitchProfile": "ਉਸ ਪ੍ਰੋਫਾਈਲ 'ਤੇ ਸਵਿੱਚ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "प्रोफ़ाइलं परिवर्तयतु",
335
+ "chooseYourProfile": "स्वप्रोफ़ाइलं चिनोतु",
336
+ "emailLinkedToMultipleProfiles": "{{email}} एकाधिकैः प्रोफ़ाइलैः सह सम्बद्धः अस्ति। यत् द्रष्टुम् इच्छति तत् चिनोतु।",
337
+ "contactLinkedToMultipleProfiles": "अयं सम्पर्कः एकाधिकैः प्रोफ़ाइलैः सह सम्बद्धः अस्ति। यत् द्रष्टुम् इच्छति तत् चिनोतु।",
338
+ "signingInEllipsis": "प्रवेशः क्रियते…",
339
+ "couldNotSwitchProfile": "तां प्रोफ़ाइलं प्रति परिवर्तनं न शक्यते स्म। कृपया पुनः प्रयत्नं करोतु।"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "ᱯᱨᱳᱯᱷᱟᱭᱤᱞ ᱵᱚᱫᱚᱞ ᱢᱮ",
335
+ "chooseYourProfile": "ᱟᱢᱟᱜ ᱯᱨᱳᱯᱷᱟᱭᱤᱞ ᱵᱟᱪᱷᱟᱣ ᱢᱮ",
336
+ "emailLinkedToMultipleProfiles": "{{email}} ᱫᱚ ᱢᱤᱫ ᱠᱷᱚᱱ ᱰᱷᱮᱨ ᱯᱨᱳᱯᱷᱟᱭᱤᱞ ᱥᱟᱶ ᱡᱩᱰᱟᱹᱣ ᱟᱠᱟᱱᱟ᱾ ᱟᱢ ᱧᱮᱞ ᱥᱟᱱᱟᱢ ᱚᱱᱟ ᱵᱟᱪᱷᱟᱣ ᱢᱮ᱾",
337
+ "contactLinkedToMultipleProfiles": "ᱱᱳᱶᱟ ᱠᱚᱱᱴᱮᱠᱴ ᱫᱚ ᱢᱤᱫ ᱠᱷᱚᱱ ᱰᱷᱮᱨ ᱯᱨᱳᱯᱷᱟᱭᱤᱞ ᱥᱟᱶ ᱡᱩᱰᱟᱹᱣ ᱟᱠᱟᱱᱟ᱾ ᱟᱢ ᱧᱮᱞ ᱥᱟᱱᱟᱢ ᱚᱱᱟ ᱵᱟᱪᱷᱟᱣ ᱢᱮ᱾",
338
+ "signingInEllipsis": "ᱥᱟᱭᱤᱱ ᱤᱱ ᱮᱫᱟᱭ…",
339
+ "couldNotSwitchProfile": "ᱚᱱᱟ ᱯᱨᱳᱯᱷᱟᱭᱤᱞ ᱛᱮ ᱥᱩᱭᱪ ᱵᱟᱝ ᱦᱩᱭ ᱞᱮᱱᱟ᱾ ᱫᱟᱭᱟ ᱠᱟᱛᱮ ᱫᱩᱠᱟᱹᱛᱮ ᱠᱩᱨᱩᱢᱩᱴᱩ ᱢᱮ᱾"
334
340
  }
@@ -330,5 +330,11 @@
330
330
  "casesCreatedCount_other": "{{count}} cases created",
331
331
  "someRequestsFailed": "Some requests failed",
332
332
  "retry": "Retry",
333
- "retrying": "Retrying..."
333
+ "retrying": "Retrying...",
334
+ "switchProfile": "پروفائل تبديل ڪريو",
335
+ "chooseYourProfile": "پنهنجو پروفائل چونڊيو",
336
+ "emailLinkedToMultipleProfiles": "{{email}} هڪ کان وڌيڪ پروفائلن سان ڳنڍيل آهي. جيڪو توهان ڏسڻ چاهيو ٿا اهو چونڊيو.",
337
+ "contactLinkedToMultipleProfiles": "هي رابطو هڪ کان وڌيڪ پروفائلن سان ڳنڍيل آهي. جيڪو توهان ڏسڻ چاهيو ٿا اهو چونڊيو.",
338
+ "signingInEllipsis": "سائن ان ٿي رهيو آهي…",
339
+ "couldNotSwitchProfile": "ان پروفائل تي سوئچ نه ٿي سگهيو. مهرباني ڪري ٻيهر ڪوشش ڪريو."
334
340
  }