From 4d17f383ae7eb44f5a8d2610b262eda79ee3e040 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 1 Dec 2023 11:53:05 +0100 Subject: [PATCH 1/2] feat: Landing page can be localized --- CHANGELOG.md | 3 ++- app/rdf/query-cube-metadata.ts | 5 ++++- app/rdf/query-utils.ts | 15 +++++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29140544b..866705648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ You can also check the [release page](https://github.com/visualize-admin/visuali ## Unreleased -Nothing yet. +- Features + - Localized cube landing pages are now supported (dcat:landingPage) 🌎 # [3.24.2] - 2023-11-28 diff --git a/app/rdf/query-cube-metadata.ts b/app/rdf/query-cube-metadata.ts index e275cabdc..6be465414 100644 --- a/app/rdf/query-cube-metadata.ts +++ b/app/rdf/query-cube-metadata.ts @@ -140,7 +140,10 @@ export const getCubeMetadata = async ( ?contactPoint ${ns.vcard.hasEmail} ?contactPointEmail . } OPTIONAL { ?iri ${ns.dcterms.publisher} ?publisher . } - OPTIONAL { ?iri ${ns.dcat.landingPage} ?landingPage . } + ${buildLocalizedSubQuery("iri", "dcat:landingPage", "landingPage", { + locale, + fallbackToNonLocalized: true, + })} OPTIONAL { ?iri ${ns.schema.expires} ?expires . } OPTIONAL { ?iri ${ns.schema.workExample} ?workExample . } `.GROUP().BY`?iri`.THEN.BY`?identifier`.THEN.BY`?title`.THEN.BY`?description` diff --git a/app/rdf/query-utils.ts b/app/rdf/query-utils.ts index aa44a610e..635f06b12 100644 --- a/app/rdf/query-utils.ts +++ b/app/rdf/query-utils.ts @@ -10,7 +10,13 @@ export const buildLocalizedSubQuery = ( s: string, p: string, o: string, - { locale }: { locale: string } + { + locale, + fallbackToNonLocalized, + }: { + locale: string; + fallbackToNonLocalized?: boolean; + } ) => { // Include the empty locale as well. const locales = getOrderedLocales(locale).concat(""); @@ -23,9 +29,10 @@ export const buildLocalizedSubQuery = ( }` ) .join("\n")} - BIND(COALESCE(${locales - .map((locale) => `?${o}_${locale}`) - .join(", ")}) AS ?${o}) + ${fallbackToNonLocalized ? `OPTIONAL { ?${s} ${p} ?${o}_raw }` : ""} + BIND(COALESCE(${locales.map((locale) => `?${o}_${locale}`).join(", ")} ${ + fallbackToNonLocalized ? `, ?${o}_raw` : `` + }) AS ?${o}) `; }; From 35912ddbecfa4b537cfb0d83e24e5645c59301f2 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 1 Dec 2023 12:41:09 +0100 Subject: [PATCH 2/2] chore: Add query-utils unit tests --- app/rdf/query-utils.spec.ts | 67 +++++++++++++++++++++++++++++++++++++ app/rdf/query-utils.ts | 20 ++++++----- 2 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 app/rdf/query-utils.spec.ts diff --git a/app/rdf/query-utils.spec.ts b/app/rdf/query-utils.spec.ts new file mode 100644 index 000000000..34ad182b1 --- /dev/null +++ b/app/rdf/query-utils.spec.ts @@ -0,0 +1,67 @@ +import { buildLocalizedSubQuery } from "./query-utils"; + +describe("buildLocalizedSubQuery", () => { + it("should build a subquery with the given locale", () => { + const subQuery = buildLocalizedSubQuery("s", "p", "o", { + locale: "it", + }); + expect(subQuery).toEqual( + // it locale must be first! + `OPTIONAL { + ?s p ?o_it . + FILTER(LANG(?o_it) = "it") +} +OPTIONAL { + ?s p ?o_de . + FILTER(LANG(?o_de) = "de") +} +OPTIONAL { + ?s p ?o_fr . + FILTER(LANG(?o_fr) = "fr") +} +OPTIONAL { + ?s p ?o_en . + FILTER(LANG(?o_en) = "en") +} +OPTIONAL { + ?s p ?o_ . + FILTER(LANG(?o_) = "") +} +BIND(COALESCE(?o_it, ?o_de, ?o_fr, ?o_en, ?o_) AS ?o)` + ); + }); + + it("should build a subquery with the given locale, falling back to non-localized property", () => { + const subQuery = buildLocalizedSubQuery("s", "p", "o", { + locale: "en", + fallbackToNonLocalized: true, + }); + expect(subQuery).toEqual( + // en locale must be first! + `OPTIONAL { + ?s p ?o_en . + FILTER(LANG(?o_en) = "en") +} +OPTIONAL { + ?s p ?o_de . + FILTER(LANG(?o_de) = "de") +} +OPTIONAL { + ?s p ?o_fr . + FILTER(LANG(?o_fr) = "fr") +} +OPTIONAL { + ?s p ?o_it . + FILTER(LANG(?o_it) = "it") +} +OPTIONAL { + ?s p ?o_ . + FILTER(LANG(?o_) = "") +} +OPTIONAL { + ?s p ?o_raw . +} +BIND(COALESCE(?o_en, ?o_de, ?o_fr, ?o_it, ?o_, ?o_raw) AS ?o)` + ); + }); +}); diff --git a/app/rdf/query-utils.ts b/app/rdf/query-utils.ts index 635f06b12..3200e5519 100644 --- a/app/rdf/query-utils.ts +++ b/app/rdf/query-utils.ts @@ -24,16 +24,20 @@ export const buildLocalizedSubQuery = ( return `${locales .map( (locale) => `OPTIONAL { - ?${s} ${p} ?${o}_${locale} . - FILTER(LANG(?${o}_${locale}) = "${locale}") - }` + ?${s} ${p} ?${o}_${locale} . + FILTER(LANG(?${o}_${locale}) = "${locale}") +}` ) - .join("\n")} - ${fallbackToNonLocalized ? `OPTIONAL { ?${s} ${p} ?${o}_raw }` : ""} - BIND(COALESCE(${locales.map((locale) => `?${o}_${locale}`).join(", ")} ${ + .join("\n")}${ + fallbackToNonLocalized + ? `\nOPTIONAL { + ?${s} ${p} ?${o}_raw . +}` + : "" + } +BIND(COALESCE(${locales.map((locale) => `?${o}_${locale}`).join(", ")}${ fallbackToNonLocalized ? `, ?${o}_raw` : `` - }) AS ?${o}) - `; + }) AS ?${o})`; }; const getOrderedLocales = (locale: string) => {