Add per-category translations and improve preferences panel preview
Introduce category-level name/description translations in the cookie banner i18n flow. Seed default translations for fr/de/es on banner creation, parse them from the stored JSON, and manage them via react-hook-form Controllers instead of a manual ref/callback pattern. Enhance the panel preview with category descriptions and all three action buttons. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -41,6 +41,8 @@ export const cookieBannerTranslationsPageQuery = graphql`
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
description
|
||||
kind
|
||||
}
|
||||
}
|
||||
@@ -79,30 +81,44 @@ export default function CookieBannerTranslationsPage({
|
||||
t => t.language === selectedLanguage,
|
||||
);
|
||||
|
||||
const translationJson = useMemo(() => {
|
||||
if (!selectedTranslation) return null;
|
||||
const { uiStrings, categoryTranslations } = useMemo(() => {
|
||||
if (!selectedTranslation) {
|
||||
return { uiStrings: null, categoryTranslations: null };
|
||||
}
|
||||
try {
|
||||
return JSON.parse(selectedTranslation.translations) as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const raw = JSON.parse(selectedTranslation.translations) as Record<string, unknown>;
|
||||
const ui: Record<string, string> = {};
|
||||
let cats: Record<string, { name: string; description: string }> | null = null;
|
||||
|
||||
for (const [k, v] of Object.entries(raw)) {
|
||||
if (k === "categories" && typeof v === "object" && v !== null) {
|
||||
cats = v as Record<string, { name: string; description: string }>;
|
||||
} else if (typeof v === "string") {
|
||||
ui[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return { uiStrings: ui, categoryTranslations: cats };
|
||||
} catch {
|
||||
return null;
|
||||
return { uiStrings: null, categoryTranslations: null };
|
||||
}
|
||||
}, [selectedTranslation]);
|
||||
|
||||
const categoryNames = useMemo(
|
||||
const categories = useMemo(
|
||||
() =>
|
||||
banner.categories.edges.map(e => ({
|
||||
id: e.node.id,
|
||||
name: e.node.name,
|
||||
slug: e.node.slug,
|
||||
description: e.node.description,
|
||||
kind: e.node.kind,
|
||||
})) ?? [],
|
||||
})),
|
||||
[banner.categories],
|
||||
);
|
||||
|
||||
const necessaryCategoryName = useMemo(
|
||||
() => categoryNames.find(c => c.kind === "NECESSARY")?.name ?? "Necessary",
|
||||
[categoryNames],
|
||||
() => categories.find(c => c.kind === "NECESSARY")?.name ?? "Necessary",
|
||||
[categories],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -129,9 +145,10 @@ export default function CookieBannerTranslationsPage({
|
||||
key={selectedLanguage}
|
||||
cookieBannerId={banner.id}
|
||||
language={selectedLanguage}
|
||||
existingTranslations={translationJson}
|
||||
existingTranslations={uiStrings}
|
||||
existingCategoryTranslations={categoryTranslations}
|
||||
showBranding={banner.showBranding}
|
||||
categoryNames={categoryNames.map(c => c.name)}
|
||||
categories={categories}
|
||||
necessaryCategoryName={necessaryCategoryName}
|
||||
/>
|
||||
|
||||
|
||||
@@ -12,19 +12,29 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
interface CategoryPreview {
|
||||
name: string;
|
||||
description: string;
|
||||
isNecessary: boolean;
|
||||
}
|
||||
|
||||
interface PanelPreviewProps {
|
||||
panelTitle: string;
|
||||
panelDescription: string;
|
||||
buttonAcceptAll: string;
|
||||
buttonRejectAll: string;
|
||||
buttonSave: string;
|
||||
categoryNames: string[];
|
||||
categories: CategoryPreview[];
|
||||
necessaryCategoryName: string;
|
||||
}
|
||||
|
||||
export function PanelPreview({
|
||||
panelTitle,
|
||||
panelDescription,
|
||||
buttonAcceptAll,
|
||||
buttonRejectAll,
|
||||
buttonSave,
|
||||
categoryNames,
|
||||
categories,
|
||||
necessaryCategoryName,
|
||||
}: PanelPreviewProps) {
|
||||
const descriptionParts = panelDescription.split(
|
||||
@@ -46,7 +56,7 @@ export function PanelPreview({
|
||||
lineHeight: 1.5,
|
||||
maxWidth: 380,
|
||||
width: "100%",
|
||||
padding: "24px",
|
||||
padding: "24px 24px 12px 24px",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
@@ -79,9 +89,9 @@ export function PanelPreview({
|
||||
</p>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
{categoryNames.map(name => (
|
||||
{categories.map(cat => (
|
||||
<div
|
||||
key={name}
|
||||
key={cat.name}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -90,18 +100,32 @@ export function PanelPreview({
|
||||
borderBottom: "1px solid var(--probo-border, #e0e0e0)",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 500 }}>{name}</span>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 2, flex: 1, minWidth: 0 }}>
|
||||
<span style={{ fontWeight: 500 }}>{cat.name}</span>
|
||||
{cat.description && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "calc(var(--probo-font-size, 14px) - 2px)",
|
||||
color: "var(--probo-text-secondary, #555555)",
|
||||
}}
|
||||
>
|
||||
{cat.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 36,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
background:
|
||||
name === necessaryCategoryName
|
||||
cat.isNecessary
|
||||
? "var(--probo-accent, #1a1a1a)"
|
||||
: "var(--probo-border, #e0e0e0)",
|
||||
position: "relative",
|
||||
cursor: "default",
|
||||
flexShrink: 0,
|
||||
marginLeft: 12,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -112,8 +136,7 @@ export function PanelPreview({
|
||||
background: "var(--probo-bg, #ffffff)",
|
||||
position: "absolute",
|
||||
top: 2,
|
||||
left:
|
||||
name === necessaryCategoryName ? 18 : 2,
|
||||
left: cat.isNecessary ? 18 : 2,
|
||||
transition: "left 0.2s",
|
||||
}}
|
||||
/>
|
||||
@@ -122,11 +145,19 @@ export function PanelPreview({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
flexWrap: "wrap",
|
||||
marginTop: 20,
|
||||
paddingBottom: 12,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
padding: "8px 10px",
|
||||
borderRadius: "var(--probo-btn-radius, 8px)",
|
||||
border: "1px solid var(--probo-accent, #1a1a1a)",
|
||||
background: "var(--probo-accent, #1a1a1a)",
|
||||
@@ -136,7 +167,46 @@ export function PanelPreview({
|
||||
fontWeight: 500,
|
||||
lineHeight: "normal",
|
||||
cursor: "pointer",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{buttonAcceptAll}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
padding: "8px 10px",
|
||||
borderRadius: "var(--probo-btn-radius, 8px)",
|
||||
border: "1px solid var(--probo-border, #e0e0e0)",
|
||||
background:
|
||||
"color-mix(in srgb, var(--probo-text, #1a1a1a) 8%, var(--probo-bg, #ffffff))",
|
||||
color: "var(--probo-text, #1a1a1a)",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "var(--probo-font-size, 14px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: "normal",
|
||||
cursor: "pointer",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{buttonRejectAll}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
padding: "8px 10px",
|
||||
borderRadius: "var(--probo-btn-radius, 8px)",
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
color: "var(--probo-accent, #1a1a1a)",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "var(--probo-font-size, 14px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: "normal",
|
||||
cursor: "pointer",
|
||||
whiteSpace: "nowrap",
|
||||
textDecoration: "underline",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{buttonSave}
|
||||
|
||||
@@ -18,15 +18,18 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { PanelPreview } from "./PanelPreview";
|
||||
import { TRANSLATION_LABELS } from "./translationDefaults";
|
||||
import type { TranslationFormValues } from "./TranslationEditor";
|
||||
import type {
|
||||
CategoryInfo,
|
||||
TranslationFormValues,
|
||||
} from "./TranslationEditor";
|
||||
|
||||
interface PanelTranslationSectionProps {
|
||||
categoryNames: string[];
|
||||
categories: CategoryInfo[];
|
||||
necessaryCategoryName: string;
|
||||
}
|
||||
|
||||
export function PanelTranslationSection({
|
||||
categoryNames,
|
||||
categories,
|
||||
necessaryCategoryName,
|
||||
}: PanelTranslationSectionProps) {
|
||||
const { __ } = useTranslate();
|
||||
@@ -34,7 +37,21 @@ export function PanelTranslationSection({
|
||||
|
||||
const panelTitle = useWatch({ control, name: "panel_title" });
|
||||
const panelDescription = useWatch({ control, name: "panel_description" });
|
||||
const buttonAcceptAll = useWatch({ control, name: "button_accept_all" });
|
||||
const buttonRejectAll = useWatch({ control, name: "button_reject_all" });
|
||||
const buttonSave = useWatch({ control, name: "button_save" });
|
||||
const categoryTranslations = useWatch({ control, name: "categories" });
|
||||
|
||||
const visibleCategories = categories.filter(c => c.kind !== "UNCATEGORISED");
|
||||
|
||||
const previewCategories = categories.map((c) => {
|
||||
const translated = categoryTranslations?.[c.id];
|
||||
return {
|
||||
name: translated?.name || c.name,
|
||||
description: translated?.description || c.description,
|
||||
isNecessary: c.kind === "NECESSARY",
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -57,9 +74,6 @@ export function PanelTranslationSection({
|
||||
render={({ field }) => (
|
||||
<Field
|
||||
label={__(TRANSLATION_LABELS.panel_description)}
|
||||
// description={__(
|
||||
// "Use {{necessary_category}} to insert the necessary category name.",
|
||||
// )}
|
||||
>
|
||||
<Textarea {...field} rows={3} />
|
||||
</Field>
|
||||
@@ -127,12 +141,53 @@ export function PanelTranslationSection({
|
||||
<PanelPreview
|
||||
panelTitle={panelTitle}
|
||||
panelDescription={panelDescription}
|
||||
buttonAcceptAll={buttonAcceptAll}
|
||||
buttonRejectAll={buttonRejectAll}
|
||||
buttonSave={buttonSave}
|
||||
categoryNames={categoryNames}
|
||||
categories={previewCategories}
|
||||
necessaryCategoryName={necessaryCategoryName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{visibleCategories.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-medium text-txt-secondary">
|
||||
{__("Category names")}
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{visibleCategories.map(cat => (
|
||||
<Card key={cat.id} className="border p-4 space-y-3">
|
||||
<div className="text-sm text-txt-secondary">
|
||||
{cat.name}
|
||||
{" "}
|
||||
<span className="text-txt-secondary/60">
|
||||
{`(${cat.slug})`}
|
||||
</span>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`categories.${cat.id}.name`}
|
||||
render={({ field }) => (
|
||||
<Field label={__("Translated name")}>
|
||||
<Input {...field} placeholder={cat.name} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`categories.${cat.id}.description`}
|
||||
render={({ field }) => (
|
||||
<Field label={__("Translated description")}>
|
||||
<Textarea {...field} placeholder={cat.description} rows={2} />
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,21 +18,40 @@ interface PlaceholderPreviewProps {
|
||||
categoryName: string;
|
||||
}
|
||||
|
||||
function LockIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlaceholderPreview({
|
||||
placeholderText,
|
||||
placeholderButton,
|
||||
categoryName,
|
||||
}: PlaceholderPreviewProps) {
|
||||
const displayText = placeholderText.replace("{{category}}", categoryName);
|
||||
const parts = placeholderText.split("{{category}}");
|
||||
const hasPlaceholder = parts.length > 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--probo-bg, #ffffff)",
|
||||
color: "var(--probo-text, #1a1a1a)",
|
||||
color: "var(--probo-text-secondary, #555555)",
|
||||
borderRadius: "var(--probo-radius, 12px)",
|
||||
boxShadow:
|
||||
"var(--probo-shadow, 0 4px 24px rgba(0, 0, 0, 0.12))",
|
||||
border: "1px dashed var(--probo-border, #e0e0e0)",
|
||||
fontFamily:
|
||||
"var(--probo-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif)",
|
||||
fontSize: "var(--probo-font-size, 14px)",
|
||||
@@ -43,47 +62,37 @@ export function PlaceholderPreview({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
gap: 12,
|
||||
textAlign: "center",
|
||||
minHeight: 120,
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: "50%",
|
||||
background:
|
||||
"color-mix(in srgb, var(--probo-text, #1a1a1a) 8%, var(--probo-bg, #ffffff))",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: 20,
|
||||
}}
|
||||
>
|
||||
🍪
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
color: "var(--probo-text-secondary, #555555)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{displayText}
|
||||
<span style={{ color: "var(--probo-text-secondary, #555555)" }}>
|
||||
<LockIcon />
|
||||
</span>
|
||||
<p style={{ margin: 0 }}>
|
||||
{hasPlaceholder
|
||||
? (
|
||||
<>
|
||||
{parts[0]}
|
||||
<strong>{categoryName}</strong>
|
||||
{parts[1]}
|
||||
</>
|
||||
)
|
||||
: placeholderText.replace("{{category}}", categoryName)}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: "var(--probo-btn-radius, 8px)",
|
||||
border: "1px solid var(--probo-border, #e0e0e0)",
|
||||
background:
|
||||
"color-mix(in srgb, var(--probo-text, #1a1a1a) 8%, var(--probo-bg, #ffffff))",
|
||||
color: "var(--probo-text, #1a1a1a)",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "var(--probo-font-size, 14px)",
|
||||
fontWeight: 500,
|
||||
lineHeight: "normal",
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "var(--probo-accent, #1a1a1a)",
|
||||
textDecoration: "underline",
|
||||
cursor: "pointer",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{placeholderButton}
|
||||
|
||||
@@ -25,7 +25,7 @@ import type { TranslationEditorMutation } from "#/__generated__/core/Translation
|
||||
import { BannerTranslationSection } from "./BannerTranslationSection";
|
||||
import { PanelTranslationSection } from "./PanelTranslationSection";
|
||||
import { PlaceholderTranslationSection } from "./PlaceholderTranslationSection";
|
||||
import { ALL_KEYS } from "./translationDefaults";
|
||||
import { ALL_KEYS, type TranslationKey } from "./translationDefaults";
|
||||
|
||||
const upsertTranslationMutation = graphql`
|
||||
mutation TranslationEditorMutation(
|
||||
@@ -49,14 +49,30 @@ const upsertTranslationMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
export type TranslationFormValues = Record<string, string>;
|
||||
export type TranslationFormValues = Record<TranslationKey, string> & {
|
||||
categories: CategoryTranslations;
|
||||
};
|
||||
|
||||
export interface CategoryInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
kind: string;
|
||||
}
|
||||
|
||||
export type CategoryTranslations = Record<
|
||||
string,
|
||||
{ name: string; description: string }
|
||||
>;
|
||||
|
||||
interface TranslationEditorProps {
|
||||
cookieBannerId: string;
|
||||
language: string;
|
||||
existingTranslations: Record<string, string> | null;
|
||||
existingCategoryTranslations: CategoryTranslations | null;
|
||||
showBranding: boolean;
|
||||
categoryNames: string[];
|
||||
categories: CategoryInfo[];
|
||||
necessaryCategoryName: string;
|
||||
}
|
||||
|
||||
@@ -64,8 +80,9 @@ export function TranslationEditor({
|
||||
cookieBannerId,
|
||||
language,
|
||||
existingTranslations,
|
||||
existingCategoryTranslations,
|
||||
showBranding,
|
||||
categoryNames,
|
||||
categories,
|
||||
necessaryCategoryName,
|
||||
}: TranslationEditorProps) {
|
||||
const { __ } = useTranslate();
|
||||
@@ -75,24 +92,51 @@ export function TranslationEditor({
|
||||
= useMutation<TranslationEditorMutation>(upsertTranslationMutation);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
const values: Record<string, string> = {};
|
||||
const translations: Record<string, string> = {};
|
||||
for (const key of ALL_KEYS) {
|
||||
values[key] = existingTranslations?.[key] ?? "";
|
||||
translations[key] = existingTranslations?.[key] ?? "";
|
||||
}
|
||||
return values;
|
||||
}, [existingTranslations]);
|
||||
|
||||
const catDefaults: CategoryTranslations = {};
|
||||
for (const cat of categories) {
|
||||
if (cat.kind === "UNCATEGORISED") continue;
|
||||
const existing = existingCategoryTranslations?.[cat.id];
|
||||
catDefaults[cat.id] = {
|
||||
name: existing?.name ?? "",
|
||||
description: existing?.description ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...translations,
|
||||
categories: catDefaults,
|
||||
} as TranslationFormValues;
|
||||
}, [existingTranslations, existingCategoryTranslations, categories]);
|
||||
|
||||
const methods = useForm<TranslationFormValues>({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const handleSave = (formData: TranslationFormValues) => {
|
||||
const { categories: catTranslations, ...translations } = formData;
|
||||
const payload: Record<string, unknown> = { ...translations };
|
||||
|
||||
const nonEmpty: CategoryTranslations = {};
|
||||
for (const [id, entry] of Object.entries(catTranslations)) {
|
||||
if (entry.name || entry.description) {
|
||||
nonEmpty[id] = entry;
|
||||
}
|
||||
}
|
||||
if (Object.keys(nonEmpty).length > 0) {
|
||||
payload.categories = nonEmpty;
|
||||
}
|
||||
|
||||
upsertTranslation({
|
||||
variables: {
|
||||
input: {
|
||||
cookieBannerId,
|
||||
language,
|
||||
translations: JSON.stringify(formData),
|
||||
translations: JSON.stringify(payload),
|
||||
},
|
||||
},
|
||||
onCompleted() {
|
||||
@@ -123,11 +167,11 @@ export function TranslationEditor({
|
||||
>
|
||||
<BannerTranslationSection showBranding={showBranding} />
|
||||
<PanelTranslationSection
|
||||
categoryNames={categoryNames}
|
||||
categories={categories}
|
||||
necessaryCategoryName={necessaryCategoryName}
|
||||
/>
|
||||
<PlaceholderTranslationSection
|
||||
exampleCategoryName={categoryNames[1] ?? categoryNames[0] ?? "Analytics"}
|
||||
exampleCategoryName={categories[1]?.name ?? categories[0]?.name ?? "Analytics"}
|
||||
/>
|
||||
|
||||
<Button type="submit" disabled={isUpserting}>
|
||||
|
||||
@@ -30,6 +30,33 @@ var defaultCategories = []struct {
|
||||
{"Uncategorised", "uncategorised", "Cookies that have not been assigned to a category yet.", coredata.CookieCategoryKindUncategorised, 4},
|
||||
}
|
||||
|
||||
var defaultCategoryTranslationsByLanguage = map[string]map[string]struct {
|
||||
Name string
|
||||
Description string
|
||||
}{
|
||||
"fr": {
|
||||
"necessary": {"Nécessaires", "Cookies essentiels au bon fonctionnement du site web."},
|
||||
"analytics": {"Analytiques", "Cookies qui aident à comprendre comment les visiteurs interagissent avec le site web."},
|
||||
"advertising": {"Publicitaires", "Cookies utilisés pour diffuser des publicités pertinentes et suivre les campagnes."},
|
||||
"functional": {"Fonctionnels", "Cookies qui permettent des fonctionnalités améliorées et la personnalisation."},
|
||||
"uncategorised": {"Non classés", "Cookies qui n'ont pas encore été assignés à une catégorie."},
|
||||
},
|
||||
"de": {
|
||||
"necessary": {"Notwendige", "Wesentliche Cookies, die für das ordnungsgemäße Funktionieren der Website erforderlich sind."},
|
||||
"analytics": {"Analytische", "Cookies, die helfen zu verstehen, wie Besucher mit der Website interagieren."},
|
||||
"advertising": {"Werbe", "Cookies, die verwendet werden, um relevante Werbung zu liefern und Kampagnen zu verfolgen."},
|
||||
"functional": {"Funktionale", "Cookies, die erweiterte Funktionalität und Personalisierung ermöglichen."},
|
||||
"uncategorised": {"Nicht kategorisiert", "Cookies, die noch keiner Kategorie zugeordnet wurden."},
|
||||
},
|
||||
"es": {
|
||||
"necessary": {"Necesarias", "Cookies esenciales necesarias para el correcto funcionamiento del sitio web."},
|
||||
"analytics": {"Analíticas", "Cookies que ayudan a entender cómo los visitantes interactúan con el sitio web."},
|
||||
"advertising": {"Publicitarias", "Cookies utilizadas para mostrar anuncios relevantes y rastrear campañas."},
|
||||
"functional": {"Funcionales", "Cookies que permiten funcionalidades mejoradas y personalización."},
|
||||
"uncategorised": {"Sin categoría", "Cookies que aún no han sido asignadas a una categoría."},
|
||||
},
|
||||
}
|
||||
|
||||
var defaultUIStringsByLanguage = map[string]map[string]string{
|
||||
"en": {
|
||||
"banner_title": "Cookie Preferences",
|
||||
|
||||
@@ -531,6 +531,7 @@ func (s *Service) CreateCookieBanner(
|
||||
return fmt.Errorf("cannot insert cookie banner: %w", err)
|
||||
}
|
||||
|
||||
slugToGID := make(map[string]gid.GID, len(defaultCategories))
|
||||
for _, dc := range defaultCategories {
|
||||
category := &coredata.CookieCategory{
|
||||
ID: gid.New(scope.GetTenantID(), coredata.CookieCategoryEntityType),
|
||||
@@ -548,12 +549,34 @@ func (s *Service) CreateCookieBanner(
|
||||
if err := category.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert default cookie category %q: %w", dc.Name, err)
|
||||
}
|
||||
|
||||
slugToGID[dc.Slug] = category.ID
|
||||
}
|
||||
|
||||
for lang, uiStrings := range defaultUIStringsByLanguage {
|
||||
translationsJSON, err := json.Marshal(uiStrings)
|
||||
blob := make(map[string]any, len(uiStrings)+1)
|
||||
for k, v := range uiStrings {
|
||||
blob[k] = v
|
||||
}
|
||||
|
||||
if catDefaults, ok := defaultCategoryTranslationsByLanguage[lang]; ok {
|
||||
catMap := make(map[string]map[string]string, len(catDefaults))
|
||||
for slug, ct := range catDefaults {
|
||||
if id, exists := slugToGID[slug]; exists {
|
||||
catMap[id.String()] = map[string]string{
|
||||
"name": ct.Name,
|
||||
"description": ct.Description,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(catMap) > 0 {
|
||||
blob["categories"] = catMap
|
||||
}
|
||||
}
|
||||
|
||||
translationsJSON, err := json.Marshal(blob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal default UI strings for %s: %w", lang, err)
|
||||
return fmt.Errorf("cannot marshal default translations for %s: %w", lang, err)
|
||||
}
|
||||
|
||||
translation := &coredata.CookieBannerTranslation{
|
||||
|
||||
Reference in New Issue
Block a user