@redacto.io/consent-sdk-react 9.2.0-beta.1 → 9.2.0-beta.2

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 (39) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/privacy-center.d.mts +6 -10
  3. package/dist/privacy-center.d.ts +6 -10
  4. package/dist/privacy-center.js +208 -850
  5. package/dist/privacy-center.mjs +208 -850
  6. package/package.json +1 -1
  7. package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +4 -4
  8. package/src/RedactoPrivacyCenter/components/Form.tsx +214 -866
  9. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +44 -219
  10. package/src/RedactoPrivacyCenter/components/types.ts +14 -20
  11. package/src/RedactoPrivacyCenter/lib/api.ts +23 -58
  12. package/src/RedactoPrivacyCenter/lib/types.ts +0 -31
  13. package/src/RedactoPrivacyCenter/locales/as/translation.json +2 -2
  14. package/src/RedactoPrivacyCenter/locales/bho/translation.json +2 -2
  15. package/src/RedactoPrivacyCenter/locales/bn/translation.json +2 -2
  16. package/src/RedactoPrivacyCenter/locales/brx/translation.json +2 -2
  17. package/src/RedactoPrivacyCenter/locales/doi/translation.json +2 -2
  18. package/src/RedactoPrivacyCenter/locales/en/translation.json +2 -2
  19. package/src/RedactoPrivacyCenter/locales/gom/translation.json +2 -2
  20. package/src/RedactoPrivacyCenter/locales/gu/translation.json +2 -2
  21. package/src/RedactoPrivacyCenter/locales/hi/translation.json +2 -2
  22. package/src/RedactoPrivacyCenter/locales/kn/translation.json +2 -2
  23. package/src/RedactoPrivacyCenter/locales/ks/translation.json +2 -2
  24. package/src/RedactoPrivacyCenter/locales/mai/translation.json +2 -2
  25. package/src/RedactoPrivacyCenter/locales/ml/translation.json +2 -2
  26. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +2 -2
  27. package/src/RedactoPrivacyCenter/locales/mr/translation.json +2 -2
  28. package/src/RedactoPrivacyCenter/locales/ne/translation.json +2 -2
  29. package/src/RedactoPrivacyCenter/locales/or/translation.json +2 -2
  30. package/src/RedactoPrivacyCenter/locales/pa/translation.json +2 -2
  31. package/src/RedactoPrivacyCenter/locales/sa/translation.json +2 -2
  32. package/src/RedactoPrivacyCenter/locales/sat/translation.json +2 -2
  33. package/src/RedactoPrivacyCenter/locales/sd/translation.json +2 -2
  34. package/src/RedactoPrivacyCenter/locales/ta/translation.json +2 -2
  35. package/src/RedactoPrivacyCenter/locales/te/translation.json +2 -2
  36. package/src/RedactoPrivacyCenter/locales/ur/translation.json +2 -2
  37. package/tests/Form.test.tsx +123 -149
  38. package/tests/ProductSelectionSection.test.tsx +39 -4
  39. package/tests/RequestSubmit.test.tsx +27 -187
@@ -1,4 +1,3 @@
1
- import type { CSSProperties, ReactNode } from 'react';
2
1
  import character from '../assets/character.svg';
3
2
  import greenTick from '../assets/greenTick.svg';
4
3
  import sentImg from '../assets/sentImg.svg';
@@ -10,234 +9,25 @@ import { Mail } from 'lucide-react';
10
9
 
11
10
  const getVar = (varName: string): string => `var(${varName})`;
12
11
 
13
- type SummaryCardProps = Readonly<{
14
- children: ReactNode;
15
- backgroundColor?: CSSProperties['backgroundColor'];
16
- borderColor?: CSSProperties['borderColor'];
17
- }>;
18
-
19
12
  /**
20
- * Shared visual container for the post-submit success / partial-success
21
- * blocks. Same shape is used for the single Case-ID card, the multi-case
22
- * list, and the failures list (with warning colors overriding the
23
- * defaults).
24
- */
25
- const SummaryCard = ({
26
- children,
27
- backgroundColor,
28
- borderColor,
29
- }: SummaryCardProps) => (
30
- <div
31
- style={{
32
- marginTop: pcStyles.mtMd.marginTop,
33
- padding: pcStyles.pSm.padding,
34
- ...(backgroundColor === undefined
35
- ? pcStyles.bgSecondary
36
- : { backgroundColor }),
37
- borderRadius: pcStyles.borderRadiusMd,
38
- border: `1px solid ${borderColor ?? pcStyles.borderColor}`,
39
- }}
40
- >
41
- {children}
42
- </div>
43
- );
44
-
45
- /**
46
- * Success / partial-success surface shown after a case (or batch of cases)
47
- * is submitted. See `RequestSubmitProps` for the shape contract.
13
+ * Success surface shown after a case is submitted. Always renders a
14
+ * single Case-ID card — the form's 1-form -> 1-case contract means
15
+ * there is no fan-out and no partial-failure surface to maintain.
48
16
  *
49
- * Rendering rules:
50
- * - Exactly one succeeded entry + no failures → single Case-ID card
51
- * (same look as the pre-P2-SDK-4 behaviour). Used by legacy single-
52
- * case paths (nomination / grievance / non-grouped DSAR fallback).
53
- * - More than one succeeded entry → list of `product_name: case_id`
54
- * rows under a "{n} cases created" header.
55
- * - Any failed entries → a separate "Some requests failed" block with
56
- * a per-row Retry button that calls `onRetry(product_uuid)`.
17
+ * The product label above the case ID is rendered only when the
18
+ * result carries one (product-scoped request types: ACCESS / ERASURE
19
+ * / CORRECTION). Nomination and grievance leave the field empty and
20
+ * the label is omitted.
57
21
  */
58
22
  export default function RequestSubmit({
59
23
  result,
60
24
  contact,
61
25
  onCreateRequest,
62
- onRetry,
63
- retryingGroupKeys,
64
26
  revokeConsentOnFulfilment,
65
27
  revokedPurposeNames,
66
28
  }: RequestSubmitProps) {
67
29
  const { t } = useTranslation();
68
30
 
69
- const succeeded = result.succeeded;
70
- const failed = result.failed;
71
- const successCount = succeeded.length;
72
- const hasFailures = failed.length > 0;
73
- const isSingleCase = successCount === 1 && !hasFailures;
74
-
75
- const renderCaseList = () => {
76
- if (successCount === 0) {
77
- return null;
78
- }
79
- if (isSingleCase) {
80
- const only = succeeded[0];
81
- return (
82
- <SummaryCard>
83
- {only.product_name !== '' && (
84
- <PCText
85
- size="sm"
86
- weight="medium"
87
- style={{
88
- marginBottom: pcStyles.mbXs.marginBottom,
89
- wordBreak: 'break-word',
90
- }}
91
- >
92
- {only.product_name}
93
- </PCText>
94
- )}
95
- <PCText
96
- size="base"
97
- weight="bold"
98
- style={{
99
- color: pcStyles.primary,
100
- wordBreak: 'break-word',
101
- marginBottom: pcStyles.mbXs.marginBottom,
102
- }}
103
- className="pc-text-responsive"
104
- >
105
- {t('caseID')}: {only.case_id}
106
- </PCText>
107
- <PCText size="xs" weight="medium" color="secondary">
108
- {t('saveThisID')}.
109
- </PCText>
110
- </SummaryCard>
111
- );
112
- }
113
- return (
114
- <SummaryCard>
115
- <PCText
116
- size="sm"
117
- weight="semibold"
118
- style={{
119
- color: pcStyles.primary,
120
- marginBottom: pcStyles.mbSm.marginBottom,
121
- }}
122
- >
123
- {t('casesCreatedCount', { count: successCount })}
124
- </PCText>
125
- <ul
126
- style={{
127
- margin: 0,
128
- padding: 0,
129
- listStyle: 'none',
130
- display: 'flex',
131
- flexDirection: 'column',
132
- gap: getVar('--pc-spacing-xs'),
133
- }}
134
- >
135
- {succeeded.map((entry) => (
136
- <li
137
- key={`${entry.group_key}:${entry.case_id}`}
138
- style={{ wordBreak: 'break-word' }}
139
- >
140
- <PCText size="sm" weight="medium">
141
- {entry.product_name}
142
- </PCText>
143
- <PCText
144
- size="sm"
145
- weight="bold"
146
- style={{ color: pcStyles.primary, wordBreak: 'break-word' }}
147
- >
148
- {t('caseID')}: {entry.case_id}
149
- </PCText>
150
- </li>
151
- ))}
152
- </ul>
153
- <PCText
154
- size="xs"
155
- weight="medium"
156
- color="secondary"
157
- style={{ marginTop: pcStyles.mtSm.marginTop }}
158
- >
159
- {t('saveThisID')}.
160
- </PCText>
161
- </SummaryCard>
162
- );
163
- };
164
-
165
- const renderFailures = () => {
166
- if (!hasFailures) {
167
- return null;
168
- }
169
- return (
170
- <SummaryCard
171
- backgroundColor={pcStyles.warningBg}
172
- borderColor={pcStyles.warningBorder}
173
- >
174
- <PCText
175
- size="sm"
176
- weight="semibold"
177
- style={{
178
- color: pcStyles.warningText,
179
- marginBottom: pcStyles.mbSm.marginBottom,
180
- }}
181
- >
182
- {t('someRequestsFailed')}
183
- </PCText>
184
- <ul
185
- style={{
186
- margin: 0,
187
- padding: 0,
188
- listStyle: 'none',
189
- display: 'flex',
190
- flexDirection: 'column',
191
- gap: getVar('--pc-spacing-sm'),
192
- }}
193
- >
194
- {failed.map((entry) => {
195
- const isRetrying =
196
- retryingGroupKeys?.has(entry.group_key) === true;
197
- return (
198
- <li
199
- key={entry.group_key}
200
- style={{
201
- display: 'flex',
202
- alignItems: 'flex-start',
203
- justifyContent: 'space-between',
204
- gap: getVar('--pc-spacing-sm'),
205
- flexWrap: 'wrap',
206
- }}
207
- >
208
- <div
209
- style={{
210
- minWidth: 0,
211
- flex: 1,
212
- wordBreak: 'break-word',
213
- }}
214
- >
215
- <PCText size="sm" weight="medium">
216
- {entry.product_name}
217
- </PCText>
218
- <PCText size="xs" weight="medium" color="secondary">
219
- {entry.error}
220
- </PCText>
221
- </div>
222
- {onRetry !== undefined && (
223
- <PCButton
224
- variant="outline"
225
- size="sm"
226
- onClick={() => onRetry(entry.group_key)}
227
- disabled={isRetrying}
228
- isLoading={isRetrying}
229
- >
230
- {isRetrying ? t('retrying') : t('retry')}
231
- </PCButton>
232
- )}
233
- </li>
234
- );
235
- })}
236
- </ul>
237
- </SummaryCard>
238
- );
239
- };
240
-
241
31
  return (
242
32
  <div style={{ marginTop: pcStyles.mtMd.marginTop, position: 'relative' }}>
243
33
  <img
@@ -325,8 +115,43 @@ export default function RequestSubmit({
325
115
  >
326
116
  {t('thankYouForSubmittingYourRequest')}.
327
117
  </PCText>
328
- {renderCaseList()}
329
- {renderFailures()}
118
+ <div
119
+ style={{
120
+ marginTop: pcStyles.mtMd.marginTop,
121
+ padding: pcStyles.pSm.padding,
122
+ ...pcStyles.bgSecondary,
123
+ borderRadius: pcStyles.borderRadiusMd,
124
+ border: `1px solid ${pcStyles.borderColor}`,
125
+ }}
126
+ >
127
+ {result.product_name !== '' && (
128
+ <PCText
129
+ size="sm"
130
+ weight="medium"
131
+ style={{
132
+ marginBottom: pcStyles.mbXs.marginBottom,
133
+ wordBreak: 'break-word',
134
+ }}
135
+ >
136
+ {result.product_name}
137
+ </PCText>
138
+ )}
139
+ <PCText
140
+ size="base"
141
+ weight="bold"
142
+ style={{
143
+ color: pcStyles.primary,
144
+ wordBreak: 'break-word',
145
+ marginBottom: pcStyles.mbXs.marginBottom,
146
+ }}
147
+ className="pc-text-responsive"
148
+ >
149
+ {t('caseID')}: {result.case_id}
150
+ </PCText>
151
+ <PCText size="xs" weight="medium" color="secondary">
152
+ {t('saveThisID')}.
153
+ </PCText>
154
+ </div>
330
155
  <div
331
156
  style={{
332
157
  ...pcStyles.flex,
@@ -1,6 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type {
3
- BatchCaseSubmitResult,
4
3
  CaseRequest,
5
4
  ConsentModalRecord,
6
5
  } from '../lib/types';
@@ -31,29 +30,24 @@ export type CaseDetailsModalProps = {
31
30
  };
32
31
 
33
32
  /**
34
- * P2-SDK-4 of TECH-3712. `RequestSubmit` is invoked from two paths:
35
- *
36
- * 1. The grouped DSAR batch path, where `result.succeeded` lists every
37
- * case_id created (one per product-group), `result.failed` lists
38
- * per-group failures, and `onRetry(groupKey)` re-issues a single
39
- * POST for the one group the user clicks retry on.
40
- * 2. The legacy single-case path (nomination / grievance / non-grouped
41
- * fallback), which wraps its lone response into a synthetic
42
- * `BatchCaseSubmitResult` with one `succeeded` entry and an empty
43
- * `failed` list. The same UI logic renders both shapes.
44
- *
45
- * Retry is keyed by `group_key` (productGroupKey on the source group),
46
- * NOT `product_uuid`, so that a product picked both directly AND via a
47
- * nominator can be retried independently. `retryingGroupKeys` lets the
48
- * parent indicate which rows are currently mid-retry (button shows the
49
- * retrying spinner copy and is disabled).
33
+ * Single-case success surface. The Form's 1-form -> 1-case contract
34
+ * means there is no fan-out and no partial-failure UI — every submit
35
+ * produces exactly one `case_id`. `product_name` / `product_uuid` are
36
+ * populated for product-scoped request types (ACCESS / ERASURE /
37
+ * CORRECTION) and empty strings for org-level types (NOMINATION /
38
+ * GRIEVANCE), in which case the success card omits the product label.
50
39
  */
40
+ export type RequestSubmitResult = Readonly<{
41
+ case_id: string;
42
+ uuid: string;
43
+ product_name: string;
44
+ product_uuid: string;
45
+ }>;
46
+
51
47
  export type RequestSubmitProps = Readonly<{
52
- result: BatchCaseSubmitResult;
48
+ result: RequestSubmitResult;
53
49
  contact: string;
54
50
  onCreateRequest: () => void;
55
- onRetry?: (groupKey: string) => void;
56
- retryingGroupKeys?: ReadonlySet<string>;
57
51
  revokeConsentOnFulfilment?: boolean;
58
52
  revokedPurposeNames?: string;
59
53
  }>;
@@ -174,17 +174,13 @@ export const getFormData = async (
174
174
  };
175
175
 
176
176
  /**
177
- * Internal-only variant of `createCaseRequest` that THROWS on non-auth
178
- * errors so callers can read the backend's `[status] message` (e.g.
179
- * `[400] invalid_data_principal`). Used by the multi-product batch path
180
- * in Form.tsx, where `Promise.allSettled` aggregates rich errors into
181
- * the per-row `failed[].error` field.
182
- *
183
- * NOT exported at the package boundary (`privacy-center.ts`) — public
184
- * consumers should use `createCaseRequest` below, which preserves the
185
- * documented `Promise<CreateCaseResponse | null>` contract.
177
+ * Public helper for creating a case request. Returns `null` on any
178
+ * non-auth error (preserves the documented
179
+ * `Promise<CreateCaseResponse | null>` contract the SDK exposes to
180
+ * consumers) — does NOT throw. Auth errors trigger a single token
181
+ * refresh + retry before falling through.
186
182
  */
187
- export const createCaseRequestUnsafe = async (
183
+ export const createCaseRequest = async (
188
184
  organizationId: string,
189
185
  workspaceId: string,
190
186
  data: UserDataRequest,
@@ -219,55 +215,24 @@ export const createCaseRequestUnsafe = async (
219
215
  if (shouldRetry) {
220
216
  // Read from sessionStorage first (synchronously updated) then fall back to context
221
217
  const newAuthToken = sessionStorage.getItem('access_token') || authContext?.accessToken || '';
222
- return await createCaseRequestServer(
223
- organizationId,
224
- workspaceId,
225
- data,
226
- newAuthToken,
227
- false,
228
- consentClient
229
- );
218
+ try {
219
+ return await createCaseRequestServer(
220
+ organizationId,
221
+ workspaceId,
222
+ data,
223
+ newAuthToken,
224
+ false,
225
+ consentClient
226
+ );
227
+ } catch (retryError) {
228
+ // The post-refresh retry can still fail (network blip, 5xx,
229
+ // expired-again token). Honor the documented `Promise<… | null>`
230
+ // contract by swallowing here rather than letting the rethrow
231
+ // escape — callers (e.g. Form.tsx) rely on null-on-error.
232
+ console.error('Error creating case request after auth refresh:', retryError);
233
+ return null;
234
+ }
230
235
  }
231
- // P2-SDK-3/4 of TECH-3712. Rethrow non-auth errors so the batch path's
232
- // `Promise.allSettled` can surface the backend's `[status] message` to
233
- // the data principal (e.g. `[400] invalid_data_principal`) via the
234
- // `failed[].error` field.
235
- console.error('Error creating case request:', error);
236
- throw error;
237
- }
238
- };
239
-
240
- /**
241
- * Public helper for creating a case request. Returns `null` on error
242
- * (preserves the original `Promise<CreateCaseResponse | null>` contract
243
- * the SDK exposes to consumers) — does NOT throw. Internally wraps
244
- * `createCaseRequestUnsafe` and swallows non-auth errors.
245
- *
246
- * The batch-submission path in Form.tsx uses `createCaseRequestUnsafe`
247
- * directly so partial-failure UI can surface per-row backend messages;
248
- * everywhere else (including the legacy single-case path) should use
249
- * this helper.
250
- */
251
- export const createCaseRequest = async (
252
- organizationId: string,
253
- workspaceId: string,
254
- data: UserDataRequest,
255
- isPopup?: boolean,
256
- useSessionAuth?: boolean,
257
- authContext?: AuthContext,
258
- consentClient?: BaseUrlContextValue['consentClient']
259
- ): Promise<CreateCaseResponse | null> => {
260
- try {
261
- return await createCaseRequestUnsafe(
262
- organizationId,
263
- workspaceId,
264
- data,
265
- isPopup,
266
- useSessionAuth,
267
- authContext,
268
- consentClient,
269
- );
270
- } catch (error) {
271
236
  console.error('Error creating case request:', error);
272
237
  return null;
273
238
  }
@@ -204,37 +204,6 @@ export type CreateCaseResponse = {
204
204
  };
205
205
  };
206
206
 
207
- /**
208
- * Aggregated result of submitting one case per product in a multi-product
209
- * form submission.
210
- *
211
- * Phase 2 of TECH-3712. The backend has no batch endpoint; the SDK Form
212
- * fans `createCaseRequest` calls out via `Promise.allSettled` and the
213
- * consumer (P2-SDK-3) aggregates the outcomes into this shape so the UI
214
- * can render partial-success states clearly.
215
- *
216
- * `group_key` is the stable selection key for the source consent group
217
- * (encodes `product_uuid` and, for nominated groups, the nominator uuid).
218
- * Use it — not `product_uuid` — to address an entry for retry/update,
219
- * since the same product can appear in both `succeeded` and `failed`
220
- * when picked directly AND via a nominator.
221
- */
222
- export type BatchCaseSubmitResult = {
223
- succeeded: Array<{
224
- group_key: string;
225
- product_uuid: string;
226
- product_name: string;
227
- case_id: string;
228
- uuid: string;
229
- }>;
230
- failed: Array<{
231
- group_key: string;
232
- product_uuid: string;
233
- product_name: string;
234
- error: string;
235
- }>;
236
- };
237
-
238
207
  export type OtpResponse = {
239
208
  uuid: string;
240
209
  to: string;
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} উদ্দেশ্য",
318
+ "activePurposes_other": "{{count}} উদ্দেশ্য",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} उद्देश्य",
318
+ "activePurposes_other": "{{count}} उद्देश्य",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} উদ্দেশ্য",
318
+ "activePurposes_other": "{{count}} উদ্দেশ্য",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} उद्देश्य",
318
+ "activePurposes_other": "{{count}} उद्देश्य",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} उद्देश्य",
318
+ "activePurposes_other": "{{count}} उद्देश्य",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -329,8 +329,8 @@
329
329
  "selectProductsForRequestGeneric": "Select a product to continue",
330
330
  "yourData": "Your data",
331
331
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
332
- "activePurposes_one": "{{count}} active purpose",
333
- "activePurposes_other": "{{count}} active purposes",
332
+ "activePurposes_one": "{{count}} purpose",
333
+ "activePurposes_other": "{{count}} purposes",
334
334
  "autoSelected": "Auto-selected",
335
335
  "noProductsAvailable": "No products available",
336
336
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} उद्देश",
318
+ "activePurposes_other": "{{count}} उद्देश",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} હેતુ",
318
+ "activePurposes_other": "{{count}} હેતુઓ",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -317,8 +317,8 @@
317
317
  "selectProductsForRequestGeneric": "Select one or more products to continue",
318
318
  "yourData": "Your data",
319
319
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
320
- "activePurposes_one": "{{count}} active purpose",
321
- "activePurposes_other": "{{count}} active purposes",
320
+ "activePurposes_one": "{{count}} उद्देश्य",
321
+ "activePurposes_other": "{{count}} उद्देश्य",
322
322
  "autoSelected": "Auto-selected",
323
323
  "noProductsAvailable": "No products available",
324
324
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} ಉದ್ದೇಶ",
318
+ "activePurposes_other": "{{count}} ಉದ್ದೇಶಗಳು",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} مقصد",
318
+ "activePurposes_other": "{{count}} مقاصد",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} उद्देश्य",
318
+ "activePurposes_other": "{{count}} उद्देश्य",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} ഉദ്ദേശ്യം",
318
+ "activePurposes_other": "{{count}} ഉദ്ദേശ്യങ്ങൾ",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",
@@ -314,8 +314,8 @@
314
314
  "selectProductsForRequestGeneric": "Select one or more products to continue",
315
315
  "yourData": "Your data",
316
316
  "actingOnBehalfOf": "Acting on behalf of {{nominator}}",
317
- "activePurposes_one": "{{count}} active purpose",
318
- "activePurposes_other": "{{count}} active purposes",
317
+ "activePurposes_one": "{{count}} ꯄꯥꯟꯗꯝ",
318
+ "activePurposes_other": "{{count}} ꯄꯥꯟꯗꯝ",
319
319
  "autoSelected": "Auto-selected",
320
320
  "noProductsAvailable": "No products available",
321
321
  "noProductsAvailableDescription": "You have no consents to manage yet.",