feat(housekeeping): localize content route headings
This commit is contained in:
1 parent
43470ebf82
commit
fc050bd3f8
14 files changed
+288
-30
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentBrandPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -118,7 +120,7 @@ export function ContentBrandPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Brand"
|
||||
title={title ?? "Brand"}
|
||||
description="Manage theme, favicon, and logo assets."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -128,6 +130,7 @@ export function ContentBrandPage({
|
||||
|
||||
export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Brand");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.brand.theme",
|
||||
params: input.match.params,
|
||||
@@ -135,6 +138,7 @@ export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentBrandPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { HousekeepingResult } from "../../../foundation/contracts";
|
||||
import type { ContentQueryData } from "../queries/content-queries";
|
||||
|
||||
export interface ContentPageProps {
|
||||
readonly title?: string;
|
||||
readonly context: import("../../../foundation/contracts").HousekeepingCapabilityContext;
|
||||
readonly result?: HousekeepingResult<ContentQueryData>;
|
||||
readonly routeId: import("../routes").ContentRouteId | null;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
||||
import { ok } from "../../../foundation/contracts";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { renderContentBrandPage } from "./brand";
|
||||
import { renderContentEditorialPage } from "./editorial";
|
||||
import { renderContentEngagementPage } from "./engagement";
|
||||
import { renderContentHelpPage } from "./help";
|
||||
import { renderContentLocalizationPage } from "./localization";
|
||||
import { renderContentMediaPage } from "./media";
|
||||
|
||||
vi.mock("../queries/content-queries", () => ({
|
||||
contentQuery: { run: vi.fn() },
|
||||
}));
|
||||
|
||||
const context: HousekeepingCapabilityContext = {
|
||||
actor: { id: 42, username: "operator", rank: 7 },
|
||||
isSuperAdmin: false,
|
||||
has: () => true,
|
||||
hasAny: () => true,
|
||||
hasAll: () => true,
|
||||
};
|
||||
|
||||
const cases = [
|
||||
[
|
||||
"content.editorial.article-create",
|
||||
"/ase-next/content/editorial/articles/new",
|
||||
"editorial",
|
||||
"New article",
|
||||
renderContentEditorialPage,
|
||||
],
|
||||
[
|
||||
"content.media.ad-create",
|
||||
"/ase-next/content/media/ads/new",
|
||||
"media",
|
||||
"New advertisement",
|
||||
renderContentMediaPage,
|
||||
],
|
||||
[
|
||||
"content.engagement.event-create",
|
||||
"/ase-next/content/engagement/events/create",
|
||||
"engagement",
|
||||
"New event",
|
||||
renderContentEngagementPage,
|
||||
],
|
||||
[
|
||||
"content.help.question-create",
|
||||
"/ase-next/content/help/questions/new",
|
||||
"help",
|
||||
"New help article",
|
||||
renderContentHelpPage,
|
||||
],
|
||||
[
|
||||
"content.brand.theme-builder",
|
||||
"/ase-next/content/brand/theme-builder",
|
||||
"brand",
|
||||
"Theme builder",
|
||||
renderContentBrandPage,
|
||||
],
|
||||
[
|
||||
"content.localization.cms",
|
||||
"/ase-next/content/localization/cms",
|
||||
"localization",
|
||||
"CMS translations",
|
||||
renderContentLocalizationPage,
|
||||
],
|
||||
] as const;
|
||||
|
||||
it.each(cases)(
|
||||
"renders localized title for %s",
|
||||
async (routeId, canonicalHref, kind, title, renderPage) => {
|
||||
vi.mocked(contentQuery.run).mockResolvedValue(
|
||||
ok({ kind, items: [], total: 0, partialDependencies: [] }, routeId),
|
||||
);
|
||||
|
||||
const page = await renderPage({
|
||||
context,
|
||||
match: {
|
||||
routeId,
|
||||
domain: "content",
|
||||
params: {},
|
||||
canonicalHref,
|
||||
},
|
||||
translate: (key) => (key === `routes.${routeId}` ? title : key),
|
||||
});
|
||||
const html = renderToStaticMarkup(page);
|
||||
|
||||
expect(html).toContain(`<h1>${title}</h1>`);
|
||||
},
|
||||
);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentEditorialPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -17,7 +19,7 @@ export function ContentEditorialPage({
|
||||
const canPages = context.has(PERMS.PAGES_EDIT);
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Editorial content"
|
||||
title={title ?? "Editorial content"}
|
||||
description="Manage articles, navigation, tags, and writable boxes."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -96,6 +98,7 @@ export function ContentEditorialPage({
|
||||
|
||||
export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Editorial content");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.editorial.articles",
|
||||
params: input.match.params,
|
||||
@@ -103,6 +106,7 @@ export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentEditorialPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentEngagementPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -131,7 +133,7 @@ export function ContentEngagementPage({
|
||||
];
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Engagement"
|
||||
title={title ?? "Engagement"}
|
||||
description="Manage events, polls, and community prefixes."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -414,6 +416,7 @@ export async function renderContentEngagementPage(
|
||||
input: HousekeepingPageInput,
|
||||
) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Engagement");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.engagement.events",
|
||||
params: input.match.params,
|
||||
@@ -421,6 +424,7 @@ export async function renderContentEngagementPage(
|
||||
});
|
||||
return (
|
||||
<ContentEngagementPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentHelpPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -72,7 +74,7 @@ export function ContentHelpPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Help content"
|
||||
title={title ?? "Help content"}
|
||||
description="Manage help questions and email templates."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -82,6 +84,7 @@ export function ContentHelpPage({
|
||||
|
||||
export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Help content");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.help.questions",
|
||||
params: input.match.params,
|
||||
@@ -89,6 +92,7 @@ export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentHelpPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentLocalizationPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -91,7 +93,7 @@ export function ContentLocalizationPage({
|
||||
) : null;
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Localization"
|
||||
title={title ?? "Localization"}
|
||||
description="Manage CMS, client, and emulator translation stores."
|
||||
result={result}
|
||||
forms={forms}
|
||||
@@ -103,6 +105,7 @@ export async function renderContentLocalizationPage(
|
||||
input: HousekeepingPageInput,
|
||||
) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Localization");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.localization.overview",
|
||||
params: input.match.params,
|
||||
@@ -110,6 +113,7 @@ export async function renderContentLocalizationPage(
|
||||
});
|
||||
return (
|
||||
<ContentLocalizationPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||
import { contentQuery } from "../queries/content-queries";
|
||||
import { ContentCommandForm } from "./content-command-form";
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "./content-page-frame";
|
||||
|
||||
export function ContentMediaPage({
|
||||
title,
|
||||
context,
|
||||
result,
|
||||
routeId,
|
||||
@@ -17,7 +19,7 @@ export function ContentMediaPage({
|
||||
const canBanners = context.has(PERMS.BANNERS_EDIT);
|
||||
return (
|
||||
<ContentPageFrame
|
||||
title="Media"
|
||||
title={title ?? "Media"}
|
||||
description="Manage photos, uploaded media, banners, and advertisements."
|
||||
result={result}
|
||||
forms={
|
||||
@@ -112,6 +114,7 @@ export function ContentMediaPage({
|
||||
|
||||
export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||
const title = resolveHousekeepingRouteTitle(input, "Media");
|
||||
const result = await contentQuery.run(input.context, {
|
||||
routeId: routeId ?? "content.media.photos",
|
||||
params: input.match.params,
|
||||
@@ -119,6 +122,7 @@ export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
||||
});
|
||||
return (
|
||||
<ContentMediaPage
|
||||
title={title}
|
||||
context={input.context}
|
||||
result={result}
|
||||
routeId={routeId}
|
||||
|
||||
@@ -253,4 +253,70 @@ describe("housekeeping localization contract", () => {
|
||||
"Stiamo preparando gli strumenti di housekeeping disponibili.",
|
||||
);
|
||||
});
|
||||
it("keeps route-specific Content copy in the primary operator locales", () => {
|
||||
expect(en.pages.housekeeping.routes.content.editorial).toEqual({
|
||||
"article-create": "New article",
|
||||
"article-detail": "Edit article",
|
||||
articles: "Articles",
|
||||
tags: "Tags",
|
||||
"writeable-boxes": "Writable boxes",
|
||||
});
|
||||
expect(nl.pages.housekeeping.routes.content.editorial).toEqual({
|
||||
"article-create": "Nieuw artikel",
|
||||
"article-detail": "Artikel bewerken",
|
||||
articles: "Artikelen",
|
||||
tags: "Tags",
|
||||
"writeable-boxes": "Bewerkbare tekstblokken",
|
||||
});
|
||||
expect(itMessages.pages.housekeeping.routes.content.editorial).toEqual({
|
||||
"article-create": "Nuovo articolo",
|
||||
"article-detail": "Modifica articolo",
|
||||
articles: "Articoli",
|
||||
tags: "Tag",
|
||||
"writeable-boxes": "Riquadri modificabili",
|
||||
});
|
||||
});
|
||||
it("uses concrete Content workflow names across the primary operator locales", () => {
|
||||
const expectations = [
|
||||
[
|
||||
en,
|
||||
"New advertisement",
|
||||
"New event",
|
||||
"New help article",
|
||||
"Theme builder",
|
||||
"CMS translations",
|
||||
],
|
||||
[
|
||||
nl,
|
||||
"Nieuwe advertentie",
|
||||
"Nieuw evenement",
|
||||
"Nieuw helpartikel",
|
||||
"Thema-bouwer",
|
||||
"CMS-vertalingen",
|
||||
],
|
||||
[
|
||||
itMessages,
|
||||
"Nuova pubblicità",
|
||||
"Nuovo evento",
|
||||
"Nuovo articolo di assistenza",
|
||||
"Editor tema",
|
||||
"Traduzioni CMS",
|
||||
],
|
||||
] as const;
|
||||
for (const [
|
||||
messages,
|
||||
media,
|
||||
engagement,
|
||||
help,
|
||||
brand,
|
||||
localization,
|
||||
] of expectations) {
|
||||
const routes = messages.pages.housekeeping.routes.content;
|
||||
expect(routes.media["ad-create"]).toBe(media);
|
||||
expect(routes.engagement["event-create"]).toBe(engagement);
|
||||
expect(routes.help["question-create"]).toBe(help);
|
||||
expect(routes.brand["theme-builder"]).toBe(brand);
|
||||
expect(routes.localization.cms).toBe(localization);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { HousekeepingRouteRenderInput } from "../routing/route-handler";
|
||||
import {
|
||||
resolveHousekeepingMessage,
|
||||
resolveHousekeepingRouteTitle,
|
||||
} from "./route-copy";
|
||||
|
||||
function input(
|
||||
translate?: (key: string) => string,
|
||||
): HousekeepingRouteRenderInput {
|
||||
return {
|
||||
context: {
|
||||
actor: { id: 42, username: "operator", rank: 7 },
|
||||
isSuperAdmin: false,
|
||||
has: () => true,
|
||||
hasAny: () => true,
|
||||
hasAll: () => true,
|
||||
},
|
||||
match: {
|
||||
routeId: "content.editorial.article-create",
|
||||
domain: "content",
|
||||
params: {},
|
||||
canonicalHref: "/ase-next/content/editorial/articles/new",
|
||||
},
|
||||
...(translate ? { translate } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
describe("Housekeeping route copy", () => {
|
||||
it("resolves the localized title from the concrete route ID", () => {
|
||||
expect(
|
||||
resolveHousekeepingRouteTitle(
|
||||
input((key) =>
|
||||
key === "routes.content.editorial.article-create"
|
||||
? "New article"
|
||||
: key,
|
||||
),
|
||||
"Editorial content",
|
||||
),
|
||||
).toBe("New article");
|
||||
});
|
||||
|
||||
it("uses the explicit fallback when translation is absent or unresolved", () => {
|
||||
expect(resolveHousekeepingRouteTitle(input(), "Editorial content")).toBe(
|
||||
"Editorial content",
|
||||
);
|
||||
expect(
|
||||
resolveHousekeepingMessage(
|
||||
input((key) => key),
|
||||
"routes.content.editorial.article-create",
|
||||
"Editorial content",
|
||||
),
|
||||
).toBe("Editorial content");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { HousekeepingRouteRenderInput } from "../routing/route-handler";
|
||||
|
||||
export function resolveHousekeepingMessage(
|
||||
input: Pick<HousekeepingRouteRenderInput, "translate">,
|
||||
key: string,
|
||||
fallback: string,
|
||||
): string {
|
||||
const value = input.translate?.(key);
|
||||
return value && value !== key ? value : fallback;
|
||||
}
|
||||
|
||||
export function resolveHousekeepingRouteTitle(
|
||||
input: Pick<HousekeepingRouteRenderInput, "match" | "translate">,
|
||||
fallback: string,
|
||||
): string {
|
||||
return resolveHousekeepingMessage(
|
||||
input,
|
||||
`routes.${input.match.routeId}`,
|
||||
fallback,
|
||||
);
|
||||
}
|
||||
@@ -3315,18 +3315,18 @@
|
||||
"content": {
|
||||
"brand": {
|
||||
"favicon": "Favicon",
|
||||
"theme-builder": "Theme Builder",
|
||||
"theme-builder": "Theme builder",
|
||||
"theme": "Theme"
|
||||
},
|
||||
"editorial": {
|
||||
"article-create": "Editorial article Create",
|
||||
"article-detail": "Editorial article Detail",
|
||||
"article-create": "New article",
|
||||
"article-detail": "Edit article",
|
||||
"articles": "Articles",
|
||||
"tags": "Tags",
|
||||
"writeable-boxes": "Editorial writeable Boxes"
|
||||
"writeable-boxes": "Writable boxes"
|
||||
},
|
||||
"engagement": {
|
||||
"event-create": "Engagement event Create",
|
||||
"event-create": "New event",
|
||||
"event-detail": "Engagement event Detail",
|
||||
"event-types": "Engagement event Types",
|
||||
"events": "Events",
|
||||
@@ -3337,18 +3337,18 @@
|
||||
},
|
||||
"help": {
|
||||
"email-templates": "Help center",
|
||||
"question-create": "Help center",
|
||||
"question-create": "New help article",
|
||||
"question-detail": "Help center",
|
||||
"questions": "Help center"
|
||||
},
|
||||
"localization": {
|
||||
"client": "Translations",
|
||||
"cms": "Translations",
|
||||
"cms": "CMS translations",
|
||||
"emulator": "Translations",
|
||||
"overview": "Translations"
|
||||
},
|
||||
"media": {
|
||||
"ad-create": "Media",
|
||||
"ad-create": "New advertisement",
|
||||
"ad-detail": "Media",
|
||||
"ads": "Advertisements",
|
||||
"banners": "Banners",
|
||||
|
||||
@@ -3315,18 +3315,18 @@
|
||||
"content": {
|
||||
"brand": {
|
||||
"favicon": "Favicon",
|
||||
"theme-builder": "Costruttore temi",
|
||||
"theme-builder": "Editor tema",
|
||||
"theme": "Tema"
|
||||
},
|
||||
"editorial": {
|
||||
"article-create": "Editorial article Create",
|
||||
"article-detail": "Editorial article Detail",
|
||||
"article-create": "Nuovo articolo",
|
||||
"article-detail": "Modifica articolo",
|
||||
"articles": "Articoli",
|
||||
"tags": "Tag",
|
||||
"writeable-boxes": "Editorial writeable Boxes"
|
||||
"writeable-boxes": "Riquadri modificabili"
|
||||
},
|
||||
"engagement": {
|
||||
"event-create": "Engagement event Create",
|
||||
"event-create": "Nuovo evento",
|
||||
"event-detail": "Engagement event Detail",
|
||||
"event-types": "Engagement event Types",
|
||||
"events": "Eventi",
|
||||
@@ -3337,18 +3337,18 @@
|
||||
},
|
||||
"help": {
|
||||
"email-templates": "Centro assistenza",
|
||||
"question-create": "Centro assistenza",
|
||||
"question-create": "Nuovo articolo di assistenza",
|
||||
"question-detail": "Centro assistenza",
|
||||
"questions": "Centro assistenza"
|
||||
},
|
||||
"localization": {
|
||||
"client": "Traduzioni",
|
||||
"cms": "Traduzioni",
|
||||
"cms": "Traduzioni CMS",
|
||||
"emulator": "Traduzioni",
|
||||
"overview": "Traduzioni"
|
||||
},
|
||||
"media": {
|
||||
"ad-create": "Media",
|
||||
"ad-create": "Nuova pubblicità",
|
||||
"ad-detail": "Media",
|
||||
"ads": "Pubblicità",
|
||||
"banners": "Banner",
|
||||
|
||||
@@ -3220,18 +3220,18 @@
|
||||
"content": {
|
||||
"brand": {
|
||||
"favicon": "Favicon",
|
||||
"theme-builder": "Themabouwer",
|
||||
"theme-builder": "Thema-bouwer",
|
||||
"theme": "Thema"
|
||||
},
|
||||
"editorial": {
|
||||
"article-create": "Editorial article Create",
|
||||
"article-detail": "Editorial article Detail",
|
||||
"article-create": "Nieuw artikel",
|
||||
"article-detail": "Artikel bewerken",
|
||||
"articles": "Artikelen",
|
||||
"tags": "Tags",
|
||||
"writeable-boxes": "Editorial writeable Boxes"
|
||||
"writeable-boxes": "Bewerkbare tekstblokken"
|
||||
},
|
||||
"engagement": {
|
||||
"event-create": "Engagement event Create",
|
||||
"event-create": "Nieuw evenement",
|
||||
"event-detail": "Engagement event Detail",
|
||||
"event-types": "Engagement event Types",
|
||||
"events": "Events",
|
||||
@@ -3242,18 +3242,18 @@
|
||||
},
|
||||
"help": {
|
||||
"email-templates": "Helpcentrum",
|
||||
"question-create": "Helpcentrum",
|
||||
"question-create": "Nieuw helpartikel",
|
||||
"question-detail": "Helpcentrum",
|
||||
"questions": "Helpcentrum"
|
||||
},
|
||||
"localization": {
|
||||
"client": "Vertalingen",
|
||||
"cms": "Vertalingen",
|
||||
"cms": "CMS-vertalingen",
|
||||
"emulator": "Vertalingen",
|
||||
"overview": "Vertalingen"
|
||||
},
|
||||
"media": {
|
||||
"ad-create": "Media",
|
||||
"ad-create": "Nieuwe advertentie",
|
||||
"ad-detail": "Media",
|
||||
"ads": "Advertenties",
|
||||
"banners": "Banners",
|
||||
|
||||
Reference in new issue
Block a user