diff --git a/packages/cookie-banner/src/components/base.ts b/packages/cookie-banner/src/components/base.ts index ef0ae5983..5b22282a3 100644 --- a/packages/cookie-banner/src/components/base.ts +++ b/packages/cookie-banner/src/components/base.ts @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. import type { CookieBannerClient } from "../client"; -import type { BannerConfig } from "../types"; +import type { BannerConfig, Regulation } from "../types"; export type ProboState = "loading" | "banner" | "panel" | "hidden"; @@ -68,6 +68,8 @@ export interface ProboRootElement extends ProboElement { readonly reopenWidget: string; readonly consentDraft: ConsentDraft; readonly gpcApplied: boolean; + readonly regulation: Regulation | null; + readonly consentMode: "OPT_IN" | "OPT_OUT" | null; setState(state: ProboState): void; updateDraft(category: string, value: boolean): void; } diff --git a/packages/cookie-banner/src/components/cookie-banner-root.ts b/packages/cookie-banner/src/components/cookie-banner-root.ts index 75826c2c2..cfa53d2da 100644 --- a/packages/cookie-banner/src/components/cookie-banner-root.ts +++ b/packages/cookie-banner/src/components/cookie-banner-root.ts @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. import { CookieBannerClient } from "../client"; -import type { BannerConfig } from "../types"; +import type { BannerConfig, Regulation } from "../types"; import { ProboElement } from "./base"; import type { ProboState, ProboRootElement, ConsentDraft } from "./base"; @@ -57,6 +57,16 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem return this._client?.gpcApplied ?? false; } + get regulation(): Regulation | null { + return this._client?.regulation ?? null; + } + + get consentMode(): "OPT_IN" | "OPT_OUT" | null { + const mode = this._config?.consent_mode; + if (mode === "OPT_IN" || mode === "OPT_OUT") return mode; + return null; + } + attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void { if (name === "reopen-widget" && oldValue !== newValue) { this.dispatchEvent( diff --git a/packages/cookie-banner/src/themed-banner/themed-banner.ts b/packages/cookie-banner/src/themed-banner/themed-banner.ts index cdbcdf523..d005aaa69 100644 --- a/packages/cookie-banner/src/themed-banner/themed-banner.ts +++ b/packages/cookie-banner/src/themed-banner/themed-banner.ts @@ -130,6 +130,7 @@ export class ProboThemedBanner extends HTMLElement { const detail = (e as CustomEvent).detail; const config = detail.config as BannerConfig; this.applyTexts(config); + this.applyLayout(config); if (!config.show_branding) { this.shadow.querySelectorAll("[data-branding]").forEach(el => { (el as HTMLElement).setAttribute("hidden", ""); @@ -224,6 +225,20 @@ export class ProboThemedBanner extends HTMLElement { } } + private applyLayout(config: BannerConfig): void { + const texts = config.texts ?? {}; + + const hideIfEmpty = (selector: string, textKey: string): void => { + if (texts[textKey]) return; + this.shadow.querySelectorAll(selector).forEach(el => { + (el as HTMLElement).hidden = true; + }); + }; + + hideIfEmpty("probo-banner probo-reject-button", "button_reject_all"); + hideIfEmpty("probo-banner probo-customize-button", "button_customize"); + } + private esc(str: string): string { return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); } diff --git a/pkg/cookiebanner/defaults.go b/pkg/cookiebanner/defaults.go index fdbb1d716..d29e3721b 100644 --- a/pkg/cookiebanner/defaults.go +++ b/pkg/cookiebanner/defaults.go @@ -119,6 +119,15 @@ var defaultUIStringsByLanguage = map[string]map[string]string{ "cookie_policy_link_text": "Cookie Policy", "placeholder_text": "This content requires {{category}} cookies.", "placeholder_button": "Manage cookie preferences", + + "banner_title_opt_out": "Cookie Notice", + "banner_description_opt_out": "We use cookies and similar technologies. You can opt out of non-essential cookies. {{cookie_policy_link}}", + "button_acknowledge": "OK", + "button_opt_out": "Do Not Sell or Share My Personal Information", + + "banner_title_notice": "Cookie Notice", + "banner_description_notice": "This site uses cookies to enhance your experience. {{cookie_policy_link}}", + "button_dismiss": "Got it", }, "fr": { "banner_title": "Préférences de cookies", @@ -137,6 +146,15 @@ var defaultUIStringsByLanguage = map[string]map[string]string{ "cookie_policy_link_text": "Politique relative aux cookies", "placeholder_text": "Ce contenu nécessite les cookies {{category}}.", "placeholder_button": "Gérer les préférences de cookies", + + "banner_title_opt_out": "Avis sur les cookies", + "banner_description_opt_out": "Nous utilisons des cookies et technologies similaires. Vous pouvez refuser les cookies non essentiels. {{cookie_policy_link}}", + "button_acknowledge": "OK", + "button_opt_out": "Ne pas vendre ni partager mes informations personnelles", + + "banner_title_notice": "Avis sur les cookies", + "banner_description_notice": "Ce site utilise des cookies pour améliorer votre expérience. {{cookie_policy_link}}", + "button_dismiss": "Compris", }, "de": { "banner_title": "Cookie-Einstellungen", @@ -155,6 +173,15 @@ var defaultUIStringsByLanguage = map[string]map[string]string{ "cookie_policy_link_text": "Cookie-Richtlinie", "placeholder_text": "Dieser Inhalt erfordert {{category}}-Cookies.", "placeholder_button": "Cookie-Einstellungen verwalten", + + "banner_title_opt_out": "Cookie-Hinweis", + "banner_description_opt_out": "Wir verwenden Cookies und ähnliche Technologien. Sie können nicht wesentliche Cookies ablehnen. {{cookie_policy_link}}", + "button_acknowledge": "OK", + "button_opt_out": "Meine persönlichen Daten nicht verkaufen oder weitergeben", + + "banner_title_notice": "Cookie-Hinweis", + "banner_description_notice": "Diese Website verwendet Cookies, um Ihre Erfahrung zu verbessern. {{cookie_policy_link}}", + "button_dismiss": "Verstanden", }, "es": { "banner_title": "Preferencias de cookies", @@ -173,5 +200,14 @@ var defaultUIStringsByLanguage = map[string]map[string]string{ "cookie_policy_link_text": "Política de cookies", "placeholder_text": "Este contenido requiere cookies de {{category}}.", "placeholder_button": "Gestionar preferencias de cookies", + + "banner_title_opt_out": "Aviso de cookies", + "banner_description_opt_out": "Utilizamos cookies y tecnologías similares. Puede optar por no recibir cookies no esenciales. {{cookie_policy_link}}", + "button_acknowledge": "OK", + "button_opt_out": "No vender ni compartir mi información personal", + + "banner_title_notice": "Aviso de cookies", + "banner_description_notice": "Este sitio utiliza cookies para mejorar su experiencia. {{cookie_policy_link}}", + "button_dismiss": "Entendido", }, } diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go index 71bc6c853..cdc15dcd9 100644 --- a/pkg/cookiebanner/service.go +++ b/pkg/cookiebanner/service.go @@ -171,6 +171,7 @@ type ( CookiePolicyURL string `json:"cookie_policy_url"` ConsentExpiryDays int `json:"consent_expiry_days"` ConsentMode string `json:"consent_mode"` + Regulation Regulation `json:"regulation"` ShowBranding bool `json:"show_branding"` Categories []coredata.CookieBannerVersionSnapshotCategory `json:"categories"` Texts map[string]string `json:"texts"` @@ -1527,6 +1528,7 @@ func (s *Service) GetActiveBannerConfig( ctx context.Context, bannerID gid.GID, lang string, + regulation Regulation, ) (*BannerConfig, error) { var config *BannerConfig @@ -1576,6 +1578,12 @@ func (s *Service) GetActiveBannerConfig( return nil, err } + config.Regulation = regulation + if cm := ConsentModeForRegulation(regulation); cm != "" { + config.ConsentMode = cm + } + remapTextsForConsentMode(config.Texts, config.ConsentMode, regulation) + return config, nil } @@ -1644,6 +1652,37 @@ func buildBannerConfig( } } +// remapTextsForConsentMode overrides the generic banner text keys with +// mode-specific variants so the client renders the appropriate copy +// without needing consent-mode awareness itself. +func remapTextsForConsentMode(texts map[string]string, consentMode string, regulation Regulation) { + if texts == nil { + return + } + + switch { + case consentMode == ConsentModeOptOut: + remapTextKey(texts, "banner_title_opt_out", "banner_title") + remapTextKey(texts, "banner_description_opt_out", "banner_description") + remapTextKey(texts, "button_acknowledge", "button_accept_all") + remapTextKey(texts, "button_opt_out", "button_customize") + texts["button_reject_all"] = "" + + case regulation == RegulationNone: + remapTextKey(texts, "banner_title_notice", "banner_title") + remapTextKey(texts, "banner_description_notice", "banner_description") + remapTextKey(texts, "button_dismiss", "button_accept_all") + texts["button_reject_all"] = "" + texts["button_customize"] = "" + } +} + +func remapTextKey(texts map[string]string, src, dst string) { + if v, ok := texts[src]; ok && v != "" { + texts[dst] = v + } +} + func (s *Service) SetShowBranding( ctx context.Context, bannerID gid.GID, diff --git a/pkg/server/api/cookiebanner/v1/handler.go b/pkg/server/api/cookiebanner/v1/handler.go index 8c782bad4..cf37ec2fb 100644 --- a/pkg/server/api/cookiebanner/v1/handler.go +++ b/pkg/server/api/cookiebanner/v1/handler.go @@ -63,11 +63,6 @@ func NewMux( return r } -type configResponse struct { - *cookiebanner.BannerConfig - Regulation cookiebanner.Regulation `json:"regulation"` -} - func (h *Handler) handleGetConfig(w http.ResponseWriter, r *http.Request) { bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID")) if err != nil { @@ -76,8 +71,9 @@ func (h *Handler) handleGetConfig(w http.ResponseWriter, r *http.Request) { } lang := r.URL.Query().Get("lang") + regulation := h.resolveRegulation(r) - config, err := h.cookieBannerSvc.GetActiveBannerConfig(r.Context(), bannerID, lang) + config, err := h.cookieBannerSvc.GetActiveBannerConfig(r.Context(), bannerID, lang, regulation) if err != nil { if errors.Is(err, cookiebanner.ErrBannerNotFound) { jsonutil.RenderNotFound(w, fmt.Errorf("banner not found")) @@ -92,15 +88,7 @@ func (h *Handler) handleGetConfig(w http.ResponseWriter, r *http.Request) { return } - regulation := h.resolveRegulation(r) - if cm := cookiebanner.ConsentModeForRegulation(regulation); cm != "" { - config.ConsentMode = cm - } - - httpserver.RenderJSON(w, http.StatusOK, configResponse{ - BannerConfig: config, - Regulation: regulation, - }) + httpserver.RenderJSON(w, http.StatusOK, config) } func (h *Handler) resolveRegulation(r *http.Request) cookiebanner.Regulation {