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 { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import { ContentCommandForm } from "./content-command-form";
|
import { ContentCommandForm } from "./content-command-form";
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentBrandPage({
|
export function ContentBrandPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -118,7 +120,7 @@ export function ContentBrandPage({
|
|||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Brand"
|
title={title ?? "Brand"}
|
||||||
description="Manage theme, favicon, and logo assets."
|
description="Manage theme, favicon, and logo assets."
|
||||||
result={result}
|
result={result}
|
||||||
forms={forms}
|
forms={forms}
|
||||||
@@ -128,6 +130,7 @@ export function ContentBrandPage({
|
|||||||
|
|
||||||
export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Brand");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.brand.theme",
|
routeId: routeId ?? "content.brand.theme",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -135,6 +138,7 @@ export async function renderContentBrandPage(input: HousekeepingPageInput) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentBrandPage
|
<ContentBrandPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { HousekeepingResult } from "../../../foundation/contracts";
|
|||||||
import type { ContentQueryData } from "../queries/content-queries";
|
import type { ContentQueryData } from "../queries/content-queries";
|
||||||
|
|
||||||
export interface ContentPageProps {
|
export interface ContentPageProps {
|
||||||
|
readonly title?: string;
|
||||||
readonly context: import("../../../foundation/contracts").HousekeepingCapabilityContext;
|
readonly context: import("../../../foundation/contracts").HousekeepingCapabilityContext;
|
||||||
readonly result?: HousekeepingResult<ContentQueryData>;
|
readonly result?: HousekeepingResult<ContentQueryData>;
|
||||||
readonly routeId: import("../routes").ContentRouteId | null;
|
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 { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import { ContentCommandForm } from "./content-command-form";
|
import { ContentCommandForm } from "./content-command-form";
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentEditorialPage({
|
export function ContentEditorialPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -17,7 +19,7 @@ export function ContentEditorialPage({
|
|||||||
const canPages = context.has(PERMS.PAGES_EDIT);
|
const canPages = context.has(PERMS.PAGES_EDIT);
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Editorial content"
|
title={title ?? "Editorial content"}
|
||||||
description="Manage articles, navigation, tags, and writable boxes."
|
description="Manage articles, navigation, tags, and writable boxes."
|
||||||
result={result}
|
result={result}
|
||||||
forms={
|
forms={
|
||||||
@@ -96,6 +98,7 @@ export function ContentEditorialPage({
|
|||||||
|
|
||||||
export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Editorial content");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.editorial.articles",
|
routeId: routeId ?? "content.editorial.articles",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -103,6 +106,7 @@ export async function renderContentEditorialPage(input: HousekeepingPageInput) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentEditorialPage
|
<ContentEditorialPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PERMS } from "@/lib/permission-slugs";
|
import { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentEngagementPage({
|
export function ContentEngagementPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -131,7 +133,7 @@ export function ContentEngagementPage({
|
|||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Engagement"
|
title={title ?? "Engagement"}
|
||||||
description="Manage events, polls, and community prefixes."
|
description="Manage events, polls, and community prefixes."
|
||||||
result={result}
|
result={result}
|
||||||
forms={
|
forms={
|
||||||
@@ -414,6 +416,7 @@ export async function renderContentEngagementPage(
|
|||||||
input: HousekeepingPageInput,
|
input: HousekeepingPageInput,
|
||||||
) {
|
) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Engagement");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.engagement.events",
|
routeId: routeId ?? "content.engagement.events",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -421,6 +424,7 @@ export async function renderContentEngagementPage(
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentEngagementPage
|
<ContentEngagementPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PERMS } from "@/lib/permission-slugs";
|
import { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import { ContentCommandForm } from "./content-command-form";
|
import { ContentCommandForm } from "./content-command-form";
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentHelpPage({
|
export function ContentHelpPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -72,7 +74,7 @@ export function ContentHelpPage({
|
|||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Help content"
|
title={title ?? "Help content"}
|
||||||
description="Manage help questions and email templates."
|
description="Manage help questions and email templates."
|
||||||
result={result}
|
result={result}
|
||||||
forms={forms}
|
forms={forms}
|
||||||
@@ -82,6 +84,7 @@ export function ContentHelpPage({
|
|||||||
|
|
||||||
export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Help content");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.help.questions",
|
routeId: routeId ?? "content.help.questions",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -89,6 +92,7 @@ export async function renderContentHelpPage(input: HousekeepingPageInput) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentHelpPage
|
<ContentHelpPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PERMS } from "@/lib/permission-slugs";
|
import { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import { ContentCommandForm } from "./content-command-form";
|
import { ContentCommandForm } from "./content-command-form";
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentLocalizationPage({
|
export function ContentLocalizationPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -91,7 +93,7 @@ export function ContentLocalizationPage({
|
|||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Localization"
|
title={title ?? "Localization"}
|
||||||
description="Manage CMS, client, and emulator translation stores."
|
description="Manage CMS, client, and emulator translation stores."
|
||||||
result={result}
|
result={result}
|
||||||
forms={forms}
|
forms={forms}
|
||||||
@@ -103,6 +105,7 @@ export async function renderContentLocalizationPage(
|
|||||||
input: HousekeepingPageInput,
|
input: HousekeepingPageInput,
|
||||||
) {
|
) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Localization");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.localization.overview",
|
routeId: routeId ?? "content.localization.overview",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -110,6 +113,7 @@ export async function renderContentLocalizationPage(
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentLocalizationPage
|
<ContentLocalizationPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { PERMS } from "@/lib/permission-slugs";
|
import { PERMS } from "@/lib/permission-slugs";
|
||||||
|
import { resolveHousekeepingRouteTitle } from "../../../foundation/page/route-copy";
|
||||||
import type { HousekeepingPageInput } from "../../../route-handlers";
|
import type { HousekeepingPageInput } from "../../../route-handlers";
|
||||||
import { contentQuery } from "../queries/content-queries";
|
import { contentQuery } from "../queries/content-queries";
|
||||||
import { ContentCommandForm } from "./content-command-form";
|
import { ContentCommandForm } from "./content-command-form";
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
} from "./content-page-frame";
|
} from "./content-page-frame";
|
||||||
|
|
||||||
export function ContentMediaPage({
|
export function ContentMediaPage({
|
||||||
|
title,
|
||||||
context,
|
context,
|
||||||
result,
|
result,
|
||||||
routeId,
|
routeId,
|
||||||
@@ -17,7 +19,7 @@ export function ContentMediaPage({
|
|||||||
const canBanners = context.has(PERMS.BANNERS_EDIT);
|
const canBanners = context.has(PERMS.BANNERS_EDIT);
|
||||||
return (
|
return (
|
||||||
<ContentPageFrame
|
<ContentPageFrame
|
||||||
title="Media"
|
title={title ?? "Media"}
|
||||||
description="Manage photos, uploaded media, banners, and advertisements."
|
description="Manage photos, uploaded media, banners, and advertisements."
|
||||||
result={result}
|
result={result}
|
||||||
forms={
|
forms={
|
||||||
@@ -112,6 +114,7 @@ export function ContentMediaPage({
|
|||||||
|
|
||||||
export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
||||||
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
const routeId = input.match.routeId as ContentPageProps["routeId"];
|
||||||
|
const title = resolveHousekeepingRouteTitle(input, "Media");
|
||||||
const result = await contentQuery.run(input.context, {
|
const result = await contentQuery.run(input.context, {
|
||||||
routeId: routeId ?? "content.media.photos",
|
routeId: routeId ?? "content.media.photos",
|
||||||
params: input.match.params,
|
params: input.match.params,
|
||||||
@@ -119,6 +122,7 @@ export async function renderContentMediaPage(input: HousekeepingPageInput) {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ContentMediaPage
|
<ContentMediaPage
|
||||||
|
title={title}
|
||||||
context={input.context}
|
context={input.context}
|
||||||
result={result}
|
result={result}
|
||||||
routeId={routeId}
|
routeId={routeId}
|
||||||
|
|||||||
@@ -253,4 +253,70 @@ describe("housekeeping localization contract", () => {
|
|||||||
"Stiamo preparando gli strumenti di housekeeping disponibili.",
|
"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": {
|
"content": {
|
||||||
"brand": {
|
"brand": {
|
||||||
"favicon": "Favicon",
|
"favicon": "Favicon",
|
||||||
"theme-builder": "Theme Builder",
|
"theme-builder": "Theme builder",
|
||||||
"theme": "Theme"
|
"theme": "Theme"
|
||||||
},
|
},
|
||||||
"editorial": {
|
"editorial": {
|
||||||
"article-create": "Editorial article Create",
|
"article-create": "New article",
|
||||||
"article-detail": "Editorial article Detail",
|
"article-detail": "Edit article",
|
||||||
"articles": "Articles",
|
"articles": "Articles",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"writeable-boxes": "Editorial writeable Boxes"
|
"writeable-boxes": "Writable boxes"
|
||||||
},
|
},
|
||||||
"engagement": {
|
"engagement": {
|
||||||
"event-create": "Engagement event Create",
|
"event-create": "New event",
|
||||||
"event-detail": "Engagement event Detail",
|
"event-detail": "Engagement event Detail",
|
||||||
"event-types": "Engagement event Types",
|
"event-types": "Engagement event Types",
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
@@ -3337,18 +3337,18 @@
|
|||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"email-templates": "Help center",
|
"email-templates": "Help center",
|
||||||
"question-create": "Help center",
|
"question-create": "New help article",
|
||||||
"question-detail": "Help center",
|
"question-detail": "Help center",
|
||||||
"questions": "Help center"
|
"questions": "Help center"
|
||||||
},
|
},
|
||||||
"localization": {
|
"localization": {
|
||||||
"client": "Translations",
|
"client": "Translations",
|
||||||
"cms": "Translations",
|
"cms": "CMS translations",
|
||||||
"emulator": "Translations",
|
"emulator": "Translations",
|
||||||
"overview": "Translations"
|
"overview": "Translations"
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"ad-create": "Media",
|
"ad-create": "New advertisement",
|
||||||
"ad-detail": "Media",
|
"ad-detail": "Media",
|
||||||
"ads": "Advertisements",
|
"ads": "Advertisements",
|
||||||
"banners": "Banners",
|
"banners": "Banners",
|
||||||
|
|||||||
@@ -3315,18 +3315,18 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"brand": {
|
"brand": {
|
||||||
"favicon": "Favicon",
|
"favicon": "Favicon",
|
||||||
"theme-builder": "Costruttore temi",
|
"theme-builder": "Editor tema",
|
||||||
"theme": "Tema"
|
"theme": "Tema"
|
||||||
},
|
},
|
||||||
"editorial": {
|
"editorial": {
|
||||||
"article-create": "Editorial article Create",
|
"article-create": "Nuovo articolo",
|
||||||
"article-detail": "Editorial article Detail",
|
"article-detail": "Modifica articolo",
|
||||||
"articles": "Articoli",
|
"articles": "Articoli",
|
||||||
"tags": "Tag",
|
"tags": "Tag",
|
||||||
"writeable-boxes": "Editorial writeable Boxes"
|
"writeable-boxes": "Riquadri modificabili"
|
||||||
},
|
},
|
||||||
"engagement": {
|
"engagement": {
|
||||||
"event-create": "Engagement event Create",
|
"event-create": "Nuovo evento",
|
||||||
"event-detail": "Engagement event Detail",
|
"event-detail": "Engagement event Detail",
|
||||||
"event-types": "Engagement event Types",
|
"event-types": "Engagement event Types",
|
||||||
"events": "Eventi",
|
"events": "Eventi",
|
||||||
@@ -3337,18 +3337,18 @@
|
|||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"email-templates": "Centro assistenza",
|
"email-templates": "Centro assistenza",
|
||||||
"question-create": "Centro assistenza",
|
"question-create": "Nuovo articolo di assistenza",
|
||||||
"question-detail": "Centro assistenza",
|
"question-detail": "Centro assistenza",
|
||||||
"questions": "Centro assistenza"
|
"questions": "Centro assistenza"
|
||||||
},
|
},
|
||||||
"localization": {
|
"localization": {
|
||||||
"client": "Traduzioni",
|
"client": "Traduzioni",
|
||||||
"cms": "Traduzioni",
|
"cms": "Traduzioni CMS",
|
||||||
"emulator": "Traduzioni",
|
"emulator": "Traduzioni",
|
||||||
"overview": "Traduzioni"
|
"overview": "Traduzioni"
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"ad-create": "Media",
|
"ad-create": "Nuova pubblicità",
|
||||||
"ad-detail": "Media",
|
"ad-detail": "Media",
|
||||||
"ads": "Pubblicità",
|
"ads": "Pubblicità",
|
||||||
"banners": "Banner",
|
"banners": "Banner",
|
||||||
|
|||||||
@@ -3220,18 +3220,18 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"brand": {
|
"brand": {
|
||||||
"favicon": "Favicon",
|
"favicon": "Favicon",
|
||||||
"theme-builder": "Themabouwer",
|
"theme-builder": "Thema-bouwer",
|
||||||
"theme": "Thema"
|
"theme": "Thema"
|
||||||
},
|
},
|
||||||
"editorial": {
|
"editorial": {
|
||||||
"article-create": "Editorial article Create",
|
"article-create": "Nieuw artikel",
|
||||||
"article-detail": "Editorial article Detail",
|
"article-detail": "Artikel bewerken",
|
||||||
"articles": "Artikelen",
|
"articles": "Artikelen",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"writeable-boxes": "Editorial writeable Boxes"
|
"writeable-boxes": "Bewerkbare tekstblokken"
|
||||||
},
|
},
|
||||||
"engagement": {
|
"engagement": {
|
||||||
"event-create": "Engagement event Create",
|
"event-create": "Nieuw evenement",
|
||||||
"event-detail": "Engagement event Detail",
|
"event-detail": "Engagement event Detail",
|
||||||
"event-types": "Engagement event Types",
|
"event-types": "Engagement event Types",
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
@@ -3242,18 +3242,18 @@
|
|||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"email-templates": "Helpcentrum",
|
"email-templates": "Helpcentrum",
|
||||||
"question-create": "Helpcentrum",
|
"question-create": "Nieuw helpartikel",
|
||||||
"question-detail": "Helpcentrum",
|
"question-detail": "Helpcentrum",
|
||||||
"questions": "Helpcentrum"
|
"questions": "Helpcentrum"
|
||||||
},
|
},
|
||||||
"localization": {
|
"localization": {
|
||||||
"client": "Vertalingen",
|
"client": "Vertalingen",
|
||||||
"cms": "Vertalingen",
|
"cms": "CMS-vertalingen",
|
||||||
"emulator": "Vertalingen",
|
"emulator": "Vertalingen",
|
||||||
"overview": "Vertalingen"
|
"overview": "Vertalingen"
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"ad-create": "Media",
|
"ad-create": "Nieuwe advertentie",
|
||||||
"ad-detail": "Media",
|
"ad-detail": "Media",
|
||||||
"ads": "Advertenties",
|
"ads": "Advertenties",
|
||||||
"banners": "Banners",
|
"banners": "Banners",
|
||||||
|
|||||||
Reference in new issue
Block a user