Skip to content

Commit

Permalink
Optimize TCF bundle with just-in-time GVL translations (#5074)
Browse files Browse the repository at this point in the history
  • Loading branch information
gilluminate authored Jul 17, 2024
1 parent fc2ea32 commit 63ced02
Show file tree
Hide file tree
Showing 28 changed files with 1,271 additions and 1,231 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The types of changes are:
- Endpoints for listing systems (GET /system) and datasets (GET /dataset) now support optional pagination [#5071](https://github.com/ethyca/fides/pull/5071)
- Messaging page will now show a notice about using global mode [#5090](https://github.com/ethyca/fides/pull/5090)
- URL for deployment instructions when the webserver is running [#5088](https://github.com/ethyca/fides/pull/5088)
- Optimize TCF bundle with just-in-time GVL translations [#5074](https://github.com/ethyca/fides/pull/5074)

### Fixed
- Fixed bug with unescaped table names in mysql queries [#5072](https://github.com/ethyca/fides/pull/5072/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const buildBaseConfig = (
id: "pri_111",
region: "us_ca",
component: "banner_and_modal",
available_locales: experienceConfig.translations?.map((t) => t.language),
experience_config: {
id: "pri_222",
regions: ["us_ca"],
Expand Down
4 changes: 2 additions & 2 deletions clients/fides-js/__tests__/__fixtures__/mock_experience.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"updated_at": "2024-01-01T12:00:00.000000+00:00",
"region": "us_ca",
"component": "banner_and_modal",
"available_locales": ["en", "es"],
"experience_config": {
"id": "pri_222",
"created_at": "2024-01-01T12:00:00.000000+00:00",
Expand Down Expand Up @@ -132,6 +133,5 @@
]
}
],
"gvl": {},
"gvl_translations": {}
"gvl": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,9 @@
}
},
"es": {
"gvlSpecificationVersion": 3,
"vendorListVersion": 40,
"tcfPolicyVersion": 4,
"lastUpdated": "2024-01-01T12:00:00Z",
"purposes": {
"1": {
Expand Down
89 changes: 13 additions & 76 deletions clients/fides-js/__tests__/lib/i18n/i18n-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
selectBestExperienceConfigTranslation,
areLocalesEqual,
localizeModalLinkText,
loadMessagesFromGVLTranslations,
} from "~/lib/i18n";
import { loadTcfMessagesFromFiles } from "~/lib/tcf/i18n/tcf-i18n-utils";
import messagesEn from "~/lib/i18n/locales/en/messages.json";
Expand Down Expand Up @@ -80,7 +81,6 @@ describe("i18n-utils", () => {
];

const mockI18n = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
activate: jest.fn((locale: Locale): void => {
mockCurrentLocale = locale;
}),
Expand Down Expand Up @@ -263,12 +263,8 @@ describe("i18n-utils", () => {

describe("loadMessagesFromExperience", () => {
it("reads all messages from experience API response and loads into the i18n catalog", () => {
const updatedLocales = loadMessagesFromExperience(
mockI18n,
mockExperience
);
loadMessagesFromExperience(mockI18n, mockExperience);
const EXPECTED_NUM_TRANSLATIONS = 2;
expect(updatedLocales).toEqual(["en", "es"]);
expect(mockI18n.load).toHaveBeenCalledTimes(EXPECTED_NUM_TRANSLATIONS);
expect(mockI18n.load).toHaveBeenCalledWith("en", mockI18nCatalogLoad[0]);
expect(mockI18n.load).toHaveBeenCalledWith("es", mockI18nCatalogLoad[1]);
Expand Down Expand Up @@ -301,15 +297,12 @@ describe("i18n-utils", () => {

// Edit the experience data to match the legacy format (w/o translations)
delete mockExpNoTranslations.experience_config.translations;
mockExpNoTranslations.available_locales = ["en"];

// Load the "no translations" version of the experience and run tests
const updatedLocales = loadMessagesFromExperience(
mockI18n,
mockExpNoTranslations as any
);
loadMessagesFromExperience(mockI18n, mockExpNoTranslations as any);

const EXPECTED_NUM_TRANSLATIONS = 1;
expect(updatedLocales).toEqual(["en"]);
expect(mockI18n.load).toHaveBeenCalledTimes(EXPECTED_NUM_TRANSLATIONS);
expect(mockI18n.load).toHaveBeenCalledWith("en", mockI18nCatalogLoad[0]);
});
Expand All @@ -322,13 +315,12 @@ describe("i18n-utils", () => {
privacy_policy_url: "https://example.com/privacy",
override_language: "en",
};
const updatedLocales = loadMessagesFromExperience(
loadMessagesFromExperience(
mockI18n,
mockExperience,
experienceTranslationOverrides
);
const EXPECTED_NUM_TRANSLATIONS = 2;
expect(updatedLocales).toEqual(["en", "es"]);
expect(mockI18n.load).toHaveBeenCalledTimes(EXPECTED_NUM_TRANSLATIONS);
expect(mockI18n.load).toHaveBeenCalledWith("en", {
...mockI18nCatalogLoad[0],
Expand All @@ -350,47 +342,35 @@ describe("i18n-utils", () => {
privacy_policy_url: "https://example.com/privacy",
override_language: "ja",
};
const updatedLocales = loadMessagesFromExperience(
loadMessagesFromExperience(
mockI18n,
mockExperience,
experienceTranslationOverrides
);
const EXPECTED_NUM_TRANSLATIONS = 2;
expect(updatedLocales).toEqual(["en", "es"]);
expect(mockI18n.load).toHaveBeenCalledTimes(EXPECTED_NUM_TRANSLATIONS);
expect(mockI18n.load).toHaveBeenCalledWith("en", mockI18nCatalogLoad[0]);
expect(mockI18n.load).toHaveBeenCalledWith("es", mockI18nCatalogLoad[1]);
});

describe("when loading from a tcf_overlay experience", () => {
it("reads all messages from gvl_translations API response and loads into the i18n catalog", () => {
it("reads all messages from gvl translations API response and loads into the i18n catalog", () => {
// Mock out a partial response for a tcf_overlay including translations
const mockExpWithGVL = JSON.parse(JSON.stringify(mockExperience));
mockExpWithGVL.experience_config.component = "tcf_overlay";
mockExpWithGVL.gvl_translations = mockGVLTranslationsJSON;

// Load all the translations
const updatedLocales = loadMessagesFromExperience(
mockI18n,
mockExpWithGVL
);
loadMessagesFromGVLTranslations(mockI18n, mockGVLTranslationsJSON, [
"en",
"es",
]);

// First, confirm that the "regular" experience_config translations are loaded
const EXPECTED_NUM_TRANSLATIONS = 2;
expect(updatedLocales).toEqual(["en", "es"]);
expect(mockI18n.load).toHaveBeenCalledTimes(EXPECTED_NUM_TRANSLATIONS);
const [, loadedMessagesEn] = mockI18n.load.mock.calls[0];
const [, loadedMessagesEs] = mockI18n.load.mock.calls[1];
expect(loadedMessagesEn).toMatchObject({
"exp.accept_button_label": "Accept Test",
"exp.acknowledge_button_label": "Acknowledge Test",
});
expect(loadedMessagesEs).toMatchObject({
"exp.accept_button_label": "Aceptar Prueba",
"exp.acknowledge_button_label": "Reconocer Prueba",
});

// Confirm that the English gvl_translations are loaded
// Confirm that the English GVL translations are loaded
const expectedMessagesEn: Record<string, RegExp> = {
// Example purposes
"exp.tcf.purposes.1.name": /^Store and\/or access/,
Expand Down Expand Up @@ -421,7 +401,7 @@ describe("i18n-utils", () => {
expect(loadedMessagesEn[id]).toMatch(regex);
});

// Confirm that the Spanish gvl_translations are loaded
// Confirm that the Spanish GVL translations are loaded
const expectedMessagesEs: Record<string, RegExp> = {
// Example purposes
"exp.tcf.purposes.1.name": /^Almacenar la información/,
Expand Down Expand Up @@ -483,49 +463,6 @@ describe("i18n-utils", () => {
expect(getRecordCounts(loadedMessagesEn)).toMatchObject(expectedCounts);
expect(getRecordCounts(loadedMessagesEs)).toMatchObject(expectedCounts);
});

it("handles a mismatch between the experience_config and gvl_translations APIs by returning only locales available in both", () => {
// Mock out a partial response for a tcf_overlay including translations
const mockExpWithGVL = JSON.parse(JSON.stringify(mockExperience));
mockExpWithGVL.experience_config.component = "tcf_overlay";
mockExpWithGVL.gvl_translations = mockGVLTranslationsJSON;

// Modify "en" to be "EN" (uppercased!), which shouldn't be treated as a mismatch!
mockExpWithGVL.experience_config.translations[0].language = "EN";

// Replace "es" with "es-MX" in the experience_config.translations to force a mismatch
mockExpWithGVL.experience_config.translations[1].language = "es-MX";

// Confirm our test setup shows a mismatch between experience_config & gvl_translations
expect(
mockExpWithGVL.experience_config.translations.map(
(e: any) => e.language
)
).toEqual(["EN", "es-MX"]);
expect(Object.keys(mockExpWithGVL.gvl_translations)).toEqual([
"en",
"es",
]);

// Load all the translations
const updatedLocales = loadMessagesFromExperience(
mockI18n,
mockExpWithGVL
);

// Confirm that only the overlapping locales are loaded
expect(updatedLocales).toEqual(["EN"]);
const [, loadedMessagesEn] = mockI18n.load.mock.calls[0];
expect(loadedMessagesEn).toMatchObject({
"exp.accept_button_label": "Accept Test",
"exp.acknowledge_button_label": "Acknowledge Test",
"exp.tcf.purposes.1.name":
"Store and/or access information on a device",
"exp.tcf.purposes.1.description": expect.stringMatching(
/^Cookies, device or similar/
),
});
});
});
});

Expand Down
3 changes: 2 additions & 1 deletion clients/fides-js/src/components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { h } from "preact";
import { I18n } from "../lib/i18n";
import { useI18n } from "../lib/i18n/i18n-context";
import { debugLog, FidesInitOptions } from "../fides";
import MenuItem from "./MenuItem";
import { FIDES_OVERLAY_WRAPPER } from "../lib/consent-constants";
import { debugLog } from "../lib/consent-utils";
import { FidesInitOptions } from "../lib/consent-types";

interface LanguageSelectorProps {
i18n: I18n;
Expand Down
8 changes: 6 additions & 2 deletions clients/fides-js/src/components/tcf/TcfVendors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,16 @@ const PagedVendorData = ({
otherVendors: VendorRecord[];
} = useMemo(
() => ({
gvlVendors: activeChunk.filter((v) => v.isGvl),
otherVendors: activeChunk.filter((v) => !v.isGvl),
gvlVendors: activeChunk?.filter((v) => v.isGvl),
otherVendors: activeChunk?.filter((v) => !v.isGvl),
}),
[activeChunk]
);

if (!activeChunk) {
return null;
}

return (
<Fragment>
<RecordsList<VendorRecord>
Expand Down
3 changes: 1 addition & 2 deletions clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Fides, FidesOptions } from "../docs";
import type { GPPFieldMapping, GPPSettings } from "./gpp/types";
import type {
GVLJson,
GVLTranslations,
TCFFeatureRecord,
TCFFeatureSave,
TCFPurposeConsentRecord,
Expand Down Expand Up @@ -414,8 +413,8 @@ export type PrivacyExperience = {
*/
experience_config?: ExperienceConfig; // NOTE: uses our client-side ExperienceConfig type
gvl?: GVLJson; // NOTE: uses our client-side GVLJson type
gvl_translations?: GVLTranslations;
meta?: ExperienceMeta;
available_locales?: string[];
};

/**
Expand Down
Loading

0 comments on commit 63ced02

Please sign in to comment.