Skip to content

Commit

Permalink
Internationalize purpose descriptions (#181)
Browse files Browse the repository at this point in the history
* internationalize purpose descriptions

* no comment

* errors

* display desc for essential

* naming and use airgap purpose title and description over default

* no camelcase

* hard code essential and fix enum

* version bump

* consistent default titles

* fix inverted

* pull custom desc if available

* pretty

* pretty

* pretty

* prettier

* post review

* fix

* prettier
  • Loading branch information
kate-kazantseva authored Oct 25, 2024
1 parent ccef9be commit 395c9bb
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 56 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/transcend-io/consent-manager-ui.git"
},
"homepage": "https://github.com/transcend-io/consent-manager-ui",
"version": "4.21.2",
"version": "4.22.0",
"license": "MIT",
"main": "build/ui",
"files": [
Expand Down
4 changes: 2 additions & 2 deletions src/components/CompleteOptionsInverted.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { h, JSX } from 'preact';
import { useState } from 'preact/hooks';
import { useIntl } from 'react-intl';
import { useAirgap, useGetPurposeMessageKeys } from '../hooks';
import { useAirgap, useGetInvertedPurposeMessageKeys } from '../hooks';
import {
messages,
completeOptionsMessages,
Expand Down Expand Up @@ -36,7 +36,7 @@ export function CompleteOptionsInverted({

// Get the tracking purposes from Airgap for display
const initialConsentSelections = getConsentSelections(airgap);
const purposeToMessageKey = useGetPurposeMessageKeys({
const purposeToMessageKey = useGetInvertedPurposeMessageKeys({
consentSelection: initialConsentSelections,
defaultPurposeToMessageKey: DEFAULT_PURPOSE_TO_INVERTED_MESSAGE_KEY,
});
Expand Down
28 changes: 23 additions & 5 deletions src/components/CompleteOptionsToggles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ import { useMemo, useState } from 'preact/hooks';
import { useIntl } from 'react-intl';
import { getConsentSelections } from '../consent-selections';
import { CONSENT_OPTIONS } from '../constants';
import { useAirgap, useGetPurposeMessageKeys } from '../hooks';
import {
useAirgap,
useGetPurposeDescriptionKeys,
useGetPurposeMessageKeys,
} from '../hooks';
import { messages } from '../messages';
import type { HandleSetViewState } from '../types';
import { CloseButton } from './CloseButton';
import { DEFAULT_PURPOSE_TO_MESSAGE_KEY, ORDER_OF_PURPOSES } from './constants';
import {
DEFAULT_PURPOSE_TO_DESCRIPTION_KEY,
DEFAULT_PURPOSE_TO_MESSAGE_KEY,
ORDER_OF_PURPOSES,
} from './constants';
import { Switch } from './Switch';

/**
Expand Down Expand Up @@ -37,7 +45,11 @@ export function CompleteOptionsToggles({
defaultPurposeToMessageKey: DEFAULT_PURPOSE_TO_MESSAGE_KEY,
});
const purposeToDescription = useMemo(() => airgap.getPurposeTypes(), []);

const purposeToDescriptionKey = useGetPurposeDescriptionKeys({
consentSelection: initialConsentSelections,
defaultPurposeToDescriptionKey: DEFAULT_PURPOSE_TO_DESCRIPTION_KEY,
airgapPurposes: purposeToDescription,
});
// Set state on the currently selected toggles
const [consentSelections, setConsentSelections] = useState(
initialConsentSelections,
Expand Down Expand Up @@ -140,7 +152,10 @@ export function CompleteOptionsToggles({
}
/>
<p className="paragraph complete-options-toggle-description">
{purposeToDescription.Essential?.description}
{formatMessage(
purposeToDescriptionKey.Essential,
globalUiVariables,
)}
</p>
</span>
{orderedSelections.map(([purpose, isChecked], idx) => (
Expand All @@ -166,7 +181,10 @@ export function CompleteOptionsToggles({
{...(idx === 0 ? { initialFocus: true } : {})}
/>
<p className="paragraph complete-options-toggle-description">
{purposeToDescription[purpose]?.description}
{formatMessage(
purposeToDescriptionKey[purpose],
globalUiVariables,
)}
</p>
</span>
))}
Expand Down
28 changes: 18 additions & 10 deletions src/components/constants.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import { DefinedMessage } from '@transcend-io/internationalization';
import {
completeOptionsMessages,
completeOptionsInvertedMessages,
} from '../messages';
import { completeOptionsInvertedMessages, purposeMessages } from '../messages';

// Mapping of purposes to the message translation key
export const DEFAULT_PURPOSE_TO_MESSAGE_KEY: Record<string, DefinedMessage> = {
Essential: completeOptionsMessages.essentialLabel,
Functional: completeOptionsMessages.functionalLabel,
Analytics: completeOptionsMessages.analyticsLabel,
Advertising: completeOptionsMessages.advertisingLabel,
SaleOfInfo: completeOptionsMessages.saleOfInfoLabel,
Essential: purposeMessages['Essential.title'],
Functional: purposeMessages['Functional.title'],
Analytics: purposeMessages['Analytics.title'],
Advertising: purposeMessages['Advertising.title'],
SaleOfInfo: purposeMessages['SaleOfInfo.title'],
};

export const DEFAULT_PURPOSE_TO_DESCRIPTION_KEY: Record<
string,
DefinedMessage
> = {
Essential: purposeMessages['Essential.description'],
Functional: purposeMessages['Functional.description'],
Analytics: purposeMessages['Analytics.description'],
Advertising: purposeMessages['Advertising.description'],
SaleOfInfo: purposeMessages['SaleOfInfo.description'],
};

export const DEFAULT_PURPOSE_TO_INVERTED_MESSAGE_KEY: Record<
string,
DefinedMessage
> = {
Essential: completeOptionsMessages.essentialLabel,
Essential: purposeMessages['Essential.title'],
Functional: completeOptionsInvertedMessages.functionalLabel,
Analytics: completeOptionsInvertedMessages.analyticsLabel,
Advertising: completeOptionsInvertedMessages.advertisingLabel,
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export * from './useLanguage';
export * from './useStickyState';
export * from './useViewState';
export * from './useAirgap';
export * from './useGetInvertedPurposeMessageKeys';
export * from './useGetPurposeDescriptionKeys';
export * from './useGetPurposeMessageKeys';
43 changes: 43 additions & 0 deletions src/hooks/useGetInvertedPurposeMessageKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ConsentSelection } from '../types';

import { useMemo } from 'preact/hooks';

import { DefinedMessage } from '@transcend-io/internationalization';

const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType';

export const useGetInvertedPurposeMessageKeys = ({
consentSelection,
defaultPurposeToMessageKey,
}: {
/** The configured airgap purpose types */
consentSelection: ConsentSelection;
/** The lookup of messages for default purpose types */
defaultPurposeToMessageKey: Record<string, DefinedMessage>;
}): Record<string, DefinedMessage> => {
const purposeToMessageKey: Record<string, DefinedMessage> = useMemo(
() =>
// the purpose type is unique for the bundle
[...Object.keys(consentSelection ?? {}), 'Essential'].reduce(
(allMessages, purposeType) => {
if (allMessages[purposeType]) {
return allMessages;
}
const purposeMessageLabel = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.title`;
return {
...allMessages,
[purposeType]: {
id: purposeMessageLabel,
defaultMessage: defaultPurposeToMessageKey[purposeType]?.defaultMessage
|| purposeType,
description: `Translatable name for purpose '${purposeType}'`,
} as DefinedMessage,
};
},
defaultPurposeToMessageKey as Record<string, DefinedMessage>,
),
[consentSelection, defaultPurposeToMessageKey],
);

return purposeToMessageKey;
};
48 changes: 48 additions & 0 deletions src/hooks/useGetPurposeDescriptionKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type {
TrackingPurposesTypes,
} from '@transcend-io/airgap.js-types';

import { ConsentSelection } from '../types';

import { useMemo } from 'preact/hooks';

import { DefinedMessage } from '@transcend-io/internationalization';

const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType';

export const useGetPurposeDescriptionKeys = ({
consentSelection,
defaultPurposeToDescriptionKey,
airgapPurposes,
}: {
/** The configured airgap purpose types */
consentSelection: ConsentSelection;
/** The lookup of messages for default purpose types */
defaultPurposeToDescriptionKey: Record<string, DefinedMessage>;
/** Airgap purposes data */
airgapPurposes: TrackingPurposesTypes;
}): Record<string, DefinedMessage> => {
const purposeToDescriptionKey: Record<string, DefinedMessage> = useMemo(
() =>
// hard-coding Essential since it's not provided by consentSelection
[...Object.keys(consentSelection ?? {}), 'Essential'].reduce((allMessages, purposeType) => {
// making sure default message for Essential is not overwritten
// by missing Essential message from airgap
if (airgapPurposes[purposeType]?.description) {
const purposeMessageDescriptionId = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.description`;
return {
...allMessages,
[purposeType]: {
id: purposeMessageDescriptionId,
defaultMessage: airgapPurposes[purposeType]?.description,
description: `Translatable description for purpose '${purposeType}'`,
} as DefinedMessage,
};
}
return {...allMessages};
}, defaultPurposeToDescriptionKey as Record<string, DefinedMessage>),
[consentSelection, defaultPurposeToDescriptionKey],
);

return purposeToDescriptionKey;
};
30 changes: 15 additions & 15 deletions src/hooks/useGetPurposeMessageKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useMemo } from 'preact/hooks';

import { DefinedMessage } from '@transcend-io/internationalization';

const CUSTOM_PURPOSE_MESSAGE_PREFIX = 'cm-ui.purpose';
const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType';

export const useGetPurposeMessageKeys = ({
consentSelection,
Expand All @@ -18,20 +18,20 @@ export const useGetPurposeMessageKeys = ({
const purposeToMessageKey: Record<string, DefinedMessage> = useMemo(
() =>
// the purpose type is unique for the bundle
Object.keys(consentSelection ?? {}).reduce((allMessages, purposeType) => {
if (allMessages[purposeType]) {
return allMessages;
}
const customPurposeMessageLabel = `${CUSTOM_PURPOSE_MESSAGE_PREFIX}.${purposeType}`;
return {
...allMessages,
[purposeType]: {
id: customPurposeMessageLabel,
defaultMessage: purposeType,
description: `Translatable name for custom purpose '${purposeType}'`,
} as DefinedMessage,
};
}, defaultPurposeToMessageKey as Record<string, DefinedMessage>),
[...Object.keys(consentSelection ?? {}), 'Essential'].reduce(
(allMessages, purposeType) => {
const purposeMessageLabel = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.title`;
return {
...allMessages,
[purposeType]: {
id: purposeMessageLabel,
defaultMessage: purposeType,
description: `Translatable name for purpose '${purposeType}'`,
} as DefinedMessage,
};
},
defaultPurposeToMessageKey as Record<string, DefinedMessage>,
),
[consentSelection, defaultPurposeToMessageKey],
);

Expand Down
74 changes: 51 additions & 23 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,32 +456,60 @@ export const bottomMenuMessages = defineMessages('ui.src.bottomMenu', {
},
});

export const purposeMessages = defineMessages('purpose.trackingType', {
'Essential.title': {
defaultMessage: 'Essential',
description: 'Text for essential purposes in CompleteOptions view state.',
},
'Essential.description': {
defaultMessage: 'No consent needed.',
description:
'Text for essential purposes description in CompleteOptions view state.',
},
'Functional.title': {
defaultMessage: 'Functional',
description:
'Text for functional purposes in CompleteOptions view state.',
},
'Functional.description': {
defaultMessage: 'Personalization, autofilled forms, etc.',
description:
'Text for functional purposes description in CompleteOptions view state.',
},
'Analytics.title': {
defaultMessage: 'Analytics',
description: 'Text for analytics purposes in CompleteOptions view state.',
},
'Analytics.description': {
defaultMessage: 'Help us learn how our site is used and how it performs.',
description:
'Text for analytics purposes description in CompleteOptions view state.',
},
'Advertising.title': {
defaultMessage: 'Advertising',
description:
'Text for advertising purposes in CompleteOptions view state.',
},
'Advertising.description': {
defaultMessage: 'Helps us and others serve ads relevant to you.',
description:
'Text for advertising purposes description in CompleteOptions view state.',
},
'SaleOfInfo.title': {
defaultMessage: 'SaleOfInfo',
description:
'Text for sale of information purposes in CompleteOptions view state.',
},
'SaleOfInfo.description': {
defaultMessage: 'Sale of personal information.',
description:
'Text for advertising purposes description in CompleteOptions view state.',
},
});

export const completeOptionsMessages = defineMessages(
'ui.src.completeOptions',
{
essentialLabel: {
defaultMessage: 'Essential purposes',
description: 'Text for essential purposes in CompleteOptions view state.',
},
functionalLabel: {
defaultMessage: 'Functionality',
description:
'Text for functional purposes in CompleteOptions view state.',
},
analyticsLabel: {
defaultMessage: 'Analytics',
description: 'Text for analytics purposes in CompleteOptions view state.',
},
advertisingLabel: {
defaultMessage: 'Advertising',
description:
'Text for advertising purposes in CompleteOptions view state.',
},
saleOfInfoLabel: {
defaultMessage: 'Sale of personal information',
description:
'Text for sale of information purposes in CompleteOptions view state.',
},
saveButtonPrimary: {
defaultMessage: 'Confirm',
description: 'Confirm button text in CompleteOptions view state.',
Expand Down

0 comments on commit 395c9bb

Please sign in to comment.