diff --git a/scripts/generators/generate-client-tool-lookup.js b/scripts/generators/generate-client-tool-lookup.js index 362aebd3..a1d343f4 100644 --- a/scripts/generators/generate-client-tool-lookup.js +++ b/scripts/generators/generate-client-tool-lookup.js @@ -12,7 +12,7 @@ const OUTPUT_PATH = path.join(ROOT, "src/generated/client-tool-lookup.ts") const TOOL_INDEX_PATH = path.join(ROOT, "src/generated/tool-index.json") const MENU_GROUP_DEF_RE = /{ key: "([^"]+)", navKey: "([^"]+)", slug: "([^"]+)", descriptionKey: "([^"]+)" }/g -const KEY_VALUE_RE = /^\s*([a-z0-9_]+):\s*"([^"]+)",?\s*$/gm +const FAMILY_GROUP_RE = /^\s*"([^"]+)":\s*"([^"]+)",?\s*$/gm const FAMILY_BY_TOOL_KEY = { ai_color_palette_generator: "images-media", asn1_der_inspector: "security-tokens", @@ -27,6 +27,18 @@ const FAMILY_BY_TOOL_KEY = { color_shades_generator: "svg-css-visual", cron_visualizer: "devops-logs", crontab_generator: "devops-logs", + css_background_pattern_generator: "svg-css-visual", + css_border_radius_generator: "svg-css-visual", + css_box_shadow_generator: "svg-css-visual", + css_checkbox_generator: "svg-css-visual", + css_clip_path_generator: "svg-css-visual", + css_cubic_bezier_generator: "svg-css-visual", + css_glassmorphism_generator: "svg-css-visual", + css_gradient_generator: "svg-css-visual", + css_loader_generator: "svg-css-visual", + css_switch_generator: "svg-css-visual", + css_text_glitch_effect_generator: "svg-css-visual", + css_triangle_generator: "svg-css-visual", csp_parser: "security-tokens", csv_diff: "data-formats", csv_json_converter: "data-formats", @@ -144,25 +156,27 @@ function parseMenuGroups() { slug: match[3], })) - const overridesBlock = source.match(/const OVERRIDE_GROUP_BY_TOOL_KEY: Record = {([\s\S]*?)}/) - if (!overridesBlock) { - throw new Error("Unable to parse OVERRIDE_GROUP_BY_TOOL_KEY from src/core/registry/menu-groups.ts") + const familyBlock = source.match(/const PRIMARY_GROUP_BY_FAMILY:[\s\S]*?=\s*{([\s\S]*?)}/) + if (!familyBlock) { + throw new Error("Unable to parse PRIMARY_GROUP_BY_FAMILY from src/core/registry/menu-groups.ts") } - const overrides = Object.fromEntries( - [...overridesBlock[1].matchAll(KEY_VALUE_RE)].map((match) => [match[1], match[2]]), + const primaryGroupByFamily = Object.fromEntries( + [...familyBlock[1].matchAll(FAMILY_GROUP_RE)].map((match) => [match[1], match[2]]), ) - return { defs, overrides } + return { defs, primaryGroupByFamily } } -function classifyToolToMenuGroup(tool, overrides) { - const overridden = overrides[tool.key] - if (overridden) return overridden - if (tool.category === "network-web") return "web_api" - if (tool.category === "formatters") return "format_validate" - if (tool.category === "generators") return "generators_ids" - return "text_content" +function classifyToolToMenuGroup(tool, primaryGroupByFamily) { + const taxonomy = getToolTaxonomy(tool) + const familyGroup = primaryGroupByFamily[taxonomy.family] + if (familyGroup) return familyGroup + + if (tool.category === "network-web") return "web_api_network" + if (tool.category === "formatters") return "data_code_formats" + if (tool.category === "generators") return "generators_calculators" + return "text_regex" } function fallbackFamily(tool) { @@ -263,13 +277,13 @@ function buildClientLookupSource() { ) const bySlug = Object.fromEntries(orderedTools.map((tool) => [tool.slug, tool.key])) - const { defs, overrides } = parseMenuGroups() + const { defs, primaryGroupByFamily } = parseMenuGroups() const menuGroups = defs.map((group) => ({ key: group.key, navKey: group.navKey, hubSlug: group.slug, items: orderedTools - .filter((tool) => classifyToolToMenuGroup(tool, overrides) === group.key) + .filter((tool) => classifyToolToMenuGroup(tool, primaryGroupByFamily) === group.key) .map((tool) => ({ key: tool.key, slug: tool.slug })), })) diff --git a/scripts/lib/ia-stability-lib.js b/scripts/lib/ia-stability-lib.js index 971b3123..e5509b69 100644 --- a/scripts/lib/ia-stability-lib.js +++ b/scripts/lib/ia-stability-lib.js @@ -23,8 +23,11 @@ function stableSort(values) { function extractMenuGroupHubs() { const source = fs.readFileSync(MENU_GROUPS_PATH, "utf8") - const matches = [...source.matchAll(/slug:\s*"([^"]+)"/g)].map((m) => m[1]) - return unique(matches).slice(0, 6) + const defsBlock = source.match(/export const MENU_GROUP_DEFS:[\s\S]*?=\s*\[([\s\S]*?)\]/) + if (!defsBlock) return [] + + const matches = [...defsBlock[1].matchAll(/slug:\s*"([^"]+)"/g)].map((m) => m[1]) + return unique(matches) } function extractToolCategoryHubs() { diff --git a/src/app/[lang]/data-code-formats/layout.tsx b/src/app/[lang]/data-code-formats/layout.tsx new file mode 100644 index 00000000..5b9a5b03 --- /dev/null +++ b/src/app/[lang]/data-code-formats/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "data-code-formats" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.data_code_formats, + description: t.categories.data_code_formats_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/data-code-formats/page.tsx b/src/app/[lang]/data-code-formats/page.tsx new file mode 100644 index 00000000..01a34c93 --- /dev/null +++ b/src/app/[lang]/data-code-formats/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function DataCodeFormatsPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/devops-logs/layout.tsx b/src/app/[lang]/devops-logs/layout.tsx new file mode 100644 index 00000000..8ea30986 --- /dev/null +++ b/src/app/[lang]/devops-logs/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "devops-logs" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.devops_logs, + description: t.categories.devops_logs_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/devops-logs/page.tsx b/src/app/[lang]/devops-logs/page.tsx new file mode 100644 index 00000000..cdd45b0f --- /dev/null +++ b/src/app/[lang]/devops-logs/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function DevopsLogsPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/encoding-crypto/layout.tsx b/src/app/[lang]/encoding-crypto/layout.tsx new file mode 100644 index 00000000..8a389315 --- /dev/null +++ b/src/app/[lang]/encoding-crypto/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "encoding-crypto" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.encoding_crypto, + description: t.categories.encoding_crypto_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/encoding-crypto/page.tsx b/src/app/[lang]/encoding-crypto/page.tsx new file mode 100644 index 00000000..f2dd7fa4 --- /dev/null +++ b/src/app/[lang]/encoding-crypto/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function EncodingCryptoPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/generators-calculators/layout.tsx b/src/app/[lang]/generators-calculators/layout.tsx new file mode 100644 index 00000000..35f09ed1 --- /dev/null +++ b/src/app/[lang]/generators-calculators/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "generators-calculators" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.generators_calculators, + description: t.categories.generators_calculators_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/generators-calculators/page.tsx b/src/app/[lang]/generators-calculators/page.tsx new file mode 100644 index 00000000..b8878075 --- /dev/null +++ b/src/app/[lang]/generators-calculators/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function GeneratorsCalculatorsPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/images-svg-css/layout.tsx b/src/app/[lang]/images-svg-css/layout.tsx new file mode 100644 index 00000000..60671c74 --- /dev/null +++ b/src/app/[lang]/images-svg-css/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "images-svg-css" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.images_svg_css, + description: t.categories.images_svg_css_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/images-svg-css/page.tsx b/src/app/[lang]/images-svg-css/page.tsx new file mode 100644 index 00000000..c731293e --- /dev/null +++ b/src/app/[lang]/images-svg-css/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function ImagesSvgCssPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/page.tsx b/src/app/[lang]/page.tsx index 4f131ead..c5437520 100644 --- a/src/app/[lang]/page.tsx +++ b/src/app/[lang]/page.tsx @@ -4,19 +4,21 @@ import { ShieldCheck, Zap, Compass, - Puzzle, - Braces, - Languages, - FileText, - Globe2, - KeyRound, - Palette, -} from "lucide-react" + Puzzle, + Braces, + KeyRound, + Palette, + Network, + TerminalSquare, + Regex, + Calculator, + Share2, +} from "lucide-react" import { notFound } from "next/navigation" import { LOCALES, isValidLocale, requireTranslationValue } from "@/core/i18n/i18n" import { SearchButton } from "./search-button" import { getTranslation } from "@/core/i18n/translations/catalog" -import { getMenuGroups } from "@/core/registry/menu-groups" +import { MENU_GROUP_DEFS, getMenuGroups } from "@/core/registry/menu-groups" import { PopularToolsSection } from "@/features/home/components/popular-tools-section" import { HomeCategoryPreview } from "@/features/home/components/home-category-preview" import { ALL_TOOLS_SECTION_ID } from "@/core/routing/all-tools-route" @@ -66,14 +68,7 @@ export default async function Home({ params }: { params: Promise<{ lang: string const commonLabels = t.common const installAppLinkLabel = requireTranslationValue(commonLabels.install_as_app, "common.install_as_app") - const categoryLinks = [ - { key: "format_validate", slug: "format-validate" }, - { key: "convert_encode", slug: "convert-encode" }, - { key: "text_content", slug: "text-content" }, - { key: "web_api", slug: "web-api" }, - { key: "generators_ids", slug: "generators-ids" }, - { key: "design_media", slug: "design-media" }, - ] as const + const categoryLinks = MENU_GROUP_DEFS const featureCards: FeatureCard[] = [ { @@ -112,29 +107,33 @@ export default async function Home({ params }: { params: Promise<{ lang: string const categoryToolCounts = Object.fromEntries( menuGroups.map((group) => [group.key, group.items.length]) ) as Record - const categoryIcons = { - format_validate: Braces, - convert_encode: Languages, - text_content: FileText, - web_api: Globe2, - generators_ids: KeyRound, - design_media: Palette, - } as const - const categoryIconClass = { - format_validate: "border-cyan-500/30 bg-cyan-500/12 text-cyan-400", - convert_encode: "border-blue-500/30 bg-blue-500/12 text-blue-400", - text_content: "border-emerald-500/30 bg-emerald-500/12 text-emerald-400", - web_api: "border-indigo-500/30 bg-indigo-500/12 text-indigo-400", - generators_ids: "border-amber-500/30 bg-amber-500/12 text-amber-400", - design_media: "border-pink-500/30 bg-pink-500/12 text-pink-400", - } as const + const categoryIcons = { + data_code_formats: Braces, + encoding_crypto: KeyRound, + web_api_network: Network, + devops_logs: TerminalSquare, + text_regex: Regex, + images_svg_css: Palette, + generators_calculators: Calculator, + social_metadata: Share2, + } as const + const categoryIconClass = { + data_code_formats: "border-cyan-500/30 bg-cyan-500/12 text-cyan-400", + encoding_crypto: "border-blue-500/30 bg-blue-500/12 text-blue-400", + web_api_network: "border-indigo-500/30 bg-indigo-500/12 text-indigo-400", + devops_logs: "border-emerald-500/30 bg-emerald-500/12 text-emerald-400", + text_regex: "border-violet-500/30 bg-violet-500/12 text-violet-400", + images_svg_css: "border-pink-500/30 bg-pink-500/12 text-pink-400", + generators_calculators: "border-amber-500/30 bg-amber-500/12 text-amber-400", + social_metadata: "border-rose-500/30 bg-rose-500/12 text-rose-400", + } as const const heroCategoryLinks = categoryLinks.map((item) => ({ ...item, toolCount: categoryToolCounts[item.key] ?? 0, })) const categoryNavLabels = Object.fromEntries( - categoryLinks.map((item) => [item.key, requireTranslationValue(t.nav[item.key], `nav.${item.key}`)]) - ) as Record<(typeof categoryLinks)[number]["key"], string> + categoryLinks.map((item) => [item.key, requireTranslationValue(t.nav[item.key], `nav.${item.key}`)]) + ) as Record<(typeof MENU_GROUP_DEFS)[number]["key"], string> const getLocalizedToolTitle = (toolKey: string) => requireTranslationValue(localizedTools[toolKey]?.title, `tools.${toolKey}.title`) const getLocalizedToolDescription = (toolKey: string) => diff --git a/src/app/[lang]/social-metadata/layout.tsx b/src/app/[lang]/social-metadata/layout.tsx new file mode 100644 index 00000000..7c9f4fb2 --- /dev/null +++ b/src/app/[lang]/social-metadata/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "social-metadata" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.social_metadata, + description: t.categories.social_metadata_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/social-metadata/page.tsx b/src/app/[lang]/social-metadata/page.tsx new file mode 100644 index 00000000..2afac20f --- /dev/null +++ b/src/app/[lang]/social-metadata/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function SocialMetadataPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/text-regex/layout.tsx b/src/app/[lang]/text-regex/layout.tsx new file mode 100644 index 00000000..c29d3bce --- /dev/null +++ b/src/app/[lang]/text-regex/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "text-regex" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.text_regex, + description: t.categories.text_regex_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/text-regex/page.tsx b/src/app/[lang]/text-regex/page.tsx new file mode 100644 index 00000000..2bb98f86 --- /dev/null +++ b/src/app/[lang]/text-regex/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function TextRegexPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/app/[lang]/web-api-network/layout.tsx b/src/app/[lang]/web-api-network/layout.tsx new file mode 100644 index 00000000..fa418ea7 --- /dev/null +++ b/src/app/[lang]/web-api-network/layout.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation" +import type { Metadata } from "next" +import { isValidLocale } from "@/core/i18n/i18n" +import { getTranslation } from "@/core/i18n/translations/catalog" +import { buildHubMetadata } from "@/core/seo/seo" + +const SLUG = "web-api-network" + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + const t = getTranslation(lang) + + return buildHubMetadata({ + lang, + slug: SLUG, + title: t.nav.web_api_network, + description: t.categories.web_api_network_desc, + }) +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/src/app/[lang]/web-api-network/page.tsx b/src/app/[lang]/web-api-network/page.tsx new file mode 100644 index 00000000..cc30feea --- /dev/null +++ b/src/app/[lang]/web-api-network/page.tsx @@ -0,0 +1,12 @@ +import { MenuGroupHub } from "@/core/seo/components/menu-group-hub" +import { isValidLocale } from "@/core/i18n/i18n" +import { notFound } from "next/navigation" + +export default async function WebApiNetworkPage({ params }: { params: Promise<{ lang: string }> }) { + const { lang } = await params + if (!isValidLocale(lang)) { + notFound() + } + + return +} diff --git a/src/components/layout/navbar-mobile-menu.tsx b/src/components/layout/navbar-mobile-menu.tsx index 1c7e72bf..ec4c37a3 100644 --- a/src/components/layout/navbar-mobile-menu.tsx +++ b/src/components/layout/navbar-mobile-menu.tsx @@ -15,12 +15,14 @@ type PageLink = { } const CATEGORY_LINKS = [ - { slug: "format-validate", key: "format_validate" }, - { slug: "convert-encode", key: "convert_encode" }, - { slug: "text-content", key: "text_content" }, - { slug: "web-api", key: "web_api" }, - { slug: "generators-ids", key: "generators_ids" }, - { slug: "design-media", key: "design_media" }, + { slug: "data-code-formats", key: "data_code_formats" }, + { slug: "encoding-crypto", key: "encoding_crypto" }, + { slug: "web-api-network", key: "web_api_network" }, + { slug: "devops-logs", key: "devops_logs" }, + { slug: "text-regex", key: "text_regex" }, + { slug: "images-svg-css", key: "images_svg_css" }, + { slug: "generators-calculators", key: "generators_calculators" }, + { slug: "social-metadata", key: "social_metadata" }, ] as const const PAGE_LINKS: PageLink[] = [ diff --git a/src/core/i18n/translations/de.json b/src/core/i18n/translations/de.json index 6636802a..08f3ca33 100644 --- a/src/core/i18n/translations/de.json +++ b/src/core/i18n/translations/de.json @@ -30,7 +30,15 @@ "text_content": "Text & Inhalt", "web_api": "Web & API", "generators_ids": "Generatoren & IDs", - "design_media": "Design & Medien" + "design_media": "Design & Medien", + "data_code_formats": "Daten- & Codeformate", + "encoding_crypto": "Kodierung & Kryptografie", + "web_api_network": "Web, API & Netzwerk", + "devops_logs": "DevOps & Protokolle", + "text_regex": "Text & reguläre Ausdrücke", + "images_svg_css": "Bilder, SVG & CSS", + "generators_calculators": "Generatoren & Rechner", + "social_metadata": "Social & Metadaten" }, "features": { "privacy_title": "100% Datenschutz von Anfang an", @@ -2544,7 +2552,15 @@ "text_content_desc": "Texte mit Zählung, Bereinigung, Case-Konvertierung und Diff-Werkzeugen bearbeiten.", "web_api_desc": "Web- und API-Flows mit JWT, Hashing, Headern, HTTP, OpenAPI und Request-Buildern debuggen.", "generators_ids_desc": "IDs, Passwörter, Zeitstempel, Secrets und Testdaten für Entwicklung erzeugen.", - "design_media_desc": "Visuelle Assets wie Code-Bilder, Farbausgaben und SVG-Material für Sharing erzeugen." + "design_media_desc": "Visuelle Assets wie Code-Bilder, Farbausgaben und SVG-Material für Sharing erzeugen.", + "data_code_formats_desc": "JSON, YAML, XML, CSV, Schemas und codeorientierte Datenformate formatieren, prüfen und konvertieren.", + "encoding_crypto_desc": "Lokal kodieren, dekodieren, hashen, Token prüfen und Zertifikats- sowie Schlüsselmaterial verarbeiten.", + "web_api_network_desc": "HTTP, OpenAPI, URLs, User-Agents, Header, robots-Regeln und Netzwerkdaten erstellen, prüfen und debuggen.", + "devops_logs_desc": "Env-Dateien parsen, Logs bereinigen, HAR-Dateien prüfen, Cron-Ausdrücke bauen und Ops-Snippets vorbereiten.", + "text_regex_desc": "Text, Unicode, Slugs, reguläre Ausdrücke und Markdown bereinigen, vergleichen, zählen, transformieren und testen.", + "images_svg_css_desc": "Bilder bearbeiten, Farben extrahieren, SVG optimieren, CSS-Visuals erzeugen und lokale Design-Assets erstellen.", + "generators_calculators_desc": "IDs, Passwörter, Zeitstempel, QR-Codes, Barcodes, Zufallslisten und Testdaten generieren.", + "social_metadata_desc": "Open-Graph-Metadaten, Social Previews, Thumbnails und teilbare Social-Media-Assets erstellen." }, "pages": { "about_title": "Über byteflow.tools", diff --git a/src/core/i18n/translations/en.json b/src/core/i18n/translations/en.json index e99f237e..89ee02ac 100644 --- a/src/core/i18n/translations/en.json +++ b/src/core/i18n/translations/en.json @@ -30,7 +30,15 @@ "text_content": "Text & Content", "web_api": "Web & API", "generators_ids": "Generators & IDs", - "design_media": "Design & Media" + "design_media": "Design & Media", + "data_code_formats": "Data & Code Formats", + "encoding_crypto": "Encoding & Crypto", + "web_api_network": "Web, API & Network", + "devops_logs": "DevOps & Logs", + "text_regex": "Text & Regex", + "images_svg_css": "Images, SVG & CSS", + "generators_calculators": "Generators & Calculators", + "social_metadata": "Social & Metadata" }, "features": { "privacy_title": "100% Privacy by Design", @@ -325,7 +333,15 @@ "text_content_desc": "Refine text content with counting, cleanup, casing, diffing, and writing-focused helper utilities.", "web_api_desc": "Debug web protocols and API flows with JWT, hashing, headers, HTTP, OpenAPI, and request builders.", "generators_ids_desc": "Generate IDs, passwords, timestamps, secrets, and utility payloads for development and testing workflows.", - "design_media_desc": "Create visual and media assets such as code images, color outputs, SVG assets, and share-ready design snippets." + "design_media_desc": "Create visual and media assets such as code images, color outputs, SVG assets, and share-ready design snippets.", + "data_code_formats_desc": "Format, inspect, and convert JSON, YAML, XML, CSV, schemas, and code-oriented data formats.", + "encoding_crypto_desc": "Encode, decode, hash, inspect tokens, and work with certificate and key material locally.", + "web_api_network_desc": "Build, inspect, and debug HTTP, OpenAPI, URLs, user agents, headers, robots rules, and network data.", + "devops_logs_desc": "Parse environment files, scrub logs, inspect HAR files, build cron expressions, and prepare operations snippets.", + "text_regex_desc": "Clean, compare, count, transform, and test text, Unicode, slugs, regular expressions, and markdown content.", + "images_svg_css_desc": "Edit images, extract colors, optimize SVG, generate CSS visuals, and create local design assets.", + "generators_calculators_desc": "Generate IDs, passwords, timestamps, QR codes, barcodes, random lists, and test data.", + "social_metadata_desc": "Create open graph metadata, social previews, thumbnails, and share-ready social media assets." }, "tools": { "json_formatter": { diff --git a/src/core/i18n/translations/fr.json b/src/core/i18n/translations/fr.json index 4d7199a3..29a8a01c 100644 --- a/src/core/i18n/translations/fr.json +++ b/src/core/i18n/translations/fr.json @@ -30,7 +30,15 @@ "text_content": "Texte & Contenu", "web_api": "Web & API", "generators_ids": "Générateurs & IDs", - "design_media": "Design & Médias" + "design_media": "Design & Médias", + "data_code_formats": "Formats données & code", + "encoding_crypto": "Encodage & Crypto", + "web_api_network": "Web, API & Réseau", + "devops_logs": "DevOps & journaux", + "text_regex": "Texte & Regex", + "images_svg_css": "Images, SVG et CSS", + "generators_calculators": "Générateurs & Calculateurs", + "social_metadata": "Social & Métadonnées" }, "features": { "privacy_title": "100% Confidentialité dès la conception", @@ -2544,7 +2552,15 @@ "text_content_desc": "Améliorez vos textes avec comptage, nettoyage, conversion de casse et comparaison de contenu.", "web_api_desc": "Déboguez les flux Web/API avec JWT, hash, en-têtes, HTTP, OpenAPI et constructeurs de requêtes.", "generators_ids_desc": "Générez IDs, mots de passe, timestamps, secrets et données utilitaires pour le développement.", - "design_media_desc": "Créez des assets visuels comme images de code, sorties couleur et ressources SVG prêtes au partage." + "design_media_desc": "Créez des assets visuels comme images de code, sorties couleur et ressources SVG prêtes au partage.", + "data_code_formats_desc": "Formatez, inspectez et convertissez JSON, YAML, XML, CSV, schémas et formats orientés code.", + "encoding_crypto_desc": "Encodez, décodez, hachez, inspectez les jetons et manipulez certificats et clés localement.", + "web_api_network_desc": "Construisez, inspectez et déboguez HTTP, OpenAPI, URL, User-Agents, en-têtes, règles robots et données réseau.", + "devops_logs_desc": "Analysez les fichiers env, nettoyez les logs, inspectez les HAR, créez des expressions cron et préparez des snippets ops.", + "text_regex_desc": "Nettoyez, comparez, comptez, transformez et testez texte, Unicode, slugs, expressions régulières et Markdown.", + "images_svg_css_desc": "Modifiez des images, extrayez des couleurs, optimisez SVG, générez des visuels CSS et créez des assets locaux.", + "generators_calculators_desc": "Générez IDs, mots de passe, timestamps, QR codes, codes-barres, listes aléatoires et données de test.", + "social_metadata_desc": "Créez des métadonnées Open Graph, aperçus sociaux, miniatures et assets prêts à partager." }, "pages": { "about_title": "À propos de byteflow.tools", diff --git a/src/core/i18n/translations/ja.json b/src/core/i18n/translations/ja.json index 2185e1b7..6b664372 100644 --- a/src/core/i18n/translations/ja.json +++ b/src/core/i18n/translations/ja.json @@ -30,7 +30,15 @@ "text_content": "テキストとコンテンツ", "web_api": "Web と API", "generators_ids": "ジェネレーターと ID", - "design_media": "デザインとメディア" + "design_media": "デザインとメディア", + "data_code_formats": "データとコード形式", + "encoding_crypto": "エンコードと暗号", + "web_api_network": "Web・API・ネットワーク", + "devops_logs": "DevOps とログ", + "text_regex": "テキストと正規表現", + "images_svg_css": "画像・SVG・CSS", + "generators_calculators": "生成と計算", + "social_metadata": "ソーシャルとメタデータ" }, "features": { "privacy_title": "100% プライバシー設計", @@ -2544,7 +2552,15 @@ "text_content_desc": "文字数カウント、空白整理、ケース変換、差分確認などのテキスト処理を提供します。", "web_api_desc": "JWT、ハッシュ、ヘッダー、HTTP、OpenAPI、リクエスト構築で Web/API を検証します。", "generators_ids_desc": "ID、パスワード、タイムスタンプ、シークレットなど開発向けデータを生成します。", - "design_media_desc": "コード画像、カラー出力、SVG 資産など共有向けのデザイン素材を作成します。" + "design_media_desc": "コード画像、カラー出力、SVG 資産など共有向けのデザイン素材を作成します。", + "data_code_formats_desc": "JSON、YAML、XML、CSV、スキーマ、コード向けデータ形式を整形・確認・変換します。", + "encoding_crypto_desc": "エンコード、デコード、ハッシュ、トークン確認、証明書と鍵データの処理をローカルで行います。", + "web_api_network_desc": "HTTP、OpenAPI、URL、User-Agent、ヘッダー、robots ルール、ネットワークデータを構築・確認・検証します。", + "devops_logs_desc": "環境ファイル、ログ、HAR、cron 式、運用向けスニペットを扱います。", + "text_regex_desc": "テキスト、Unicode、slug、正規表現、Markdown をクリーンアップ、比較、集計、変換、検証します。", + "images_svg_css_desc": "画像編集、色抽出、SVG 最適化、CSS ビジュアル生成、ローカルなデザイン素材作成を行います。", + "generators_calculators_desc": "ID、パスワード、タイムスタンプ、QR コード、バーコード、ランダムリスト、テストデータを生成します。", + "social_metadata_desc": "Open Graph メタデータ、ソーシャルプレビュー、サムネイル、共有用ソーシャル素材を作成します。" }, "pages": { "about_title": "byteflow.toolsについて", diff --git a/src/core/i18n/translations/ko.json b/src/core/i18n/translations/ko.json index 9789d9ab..6a5e2112 100644 --- a/src/core/i18n/translations/ko.json +++ b/src/core/i18n/translations/ko.json @@ -30,7 +30,15 @@ "text_content": "텍스트 및 콘텐츠", "web_api": "웹 및 API", "generators_ids": "생성기 및 ID", - "design_media": "디자인 및 미디어" + "design_media": "디자인 및 미디어", + "data_code_formats": "데이터 및 코드 형식", + "encoding_crypto": "인코딩 및 암호화", + "web_api_network": "웹, API 및 네트워크", + "devops_logs": "DevOps 및 로그", + "text_regex": "텍스트 및 정규식", + "images_svg_css": "이미지, SVG 및 CSS", + "generators_calculators": "생성기 및 계산기", + "social_metadata": "소셜 및 메타데이터" }, "features": { "privacy_title": "100% 프라이버시 설계", @@ -2544,7 +2552,15 @@ "text_content_desc": "글자 수 계산, 공백 정리, 케이스 변환, diff 비교 등 텍스트 처리 도구를 제공합니다.", "web_api_desc": "JWT, 해시, 헤더, HTTP, OpenAPI, 요청 빌더로 Web/API를 디버깅합니다.", "generators_ids_desc": "ID, 비밀번호, 타임스탬프, 시크릿 등 개발/테스트 데이터를 생성합니다.", - "design_media_desc": "코드 이미지, 색상 출력, SVG 자산 등 공유 가능한 디자인/미디어 결과를 만듭니다." + "design_media_desc": "코드 이미지, 색상 출력, SVG 자산 등 공유 가능한 디자인/미디어 결과를 만듭니다.", + "data_code_formats_desc": "JSON, YAML, XML, CSV, 스키마, 코드 중심 데이터 형식을 포맷, 검사, 변환합니다.", + "encoding_crypto_desc": "인코딩, 디코딩, 해시, 토큰 검사, 인증서와 키 자료 처리를 로컬에서 수행합니다.", + "web_api_network_desc": "HTTP, OpenAPI, URL, User-Agent, 헤더, robots 규칙, 네트워크 데이터를 작성하고 디버깅합니다.", + "devops_logs_desc": "환경 파일, 로그 스크러빙, HAR 검사, cron 표현식, 운영 스니펫을 다룹니다.", + "text_regex_desc": "텍스트, Unicode, slug, 정규식, Markdown 콘텐츠를 정리, 비교, 계산, 변환, 테스트합니다.", + "images_svg_css_desc": "이미지 편집, 색상 추출, SVG 최적화, CSS 비주얼 생성, 로컬 디자인 에셋 제작을 지원합니다.", + "generators_calculators_desc": "ID, 비밀번호, 타임스탬프, QR 코드, 바코드, 랜덤 목록, 테스트 데이터를 생성합니다.", + "social_metadata_desc": "Open Graph 메타데이터, 소셜 미리보기, 썸네일, 공유용 소셜 미디어 에셋을 만듭니다." }, "pages": { "about_title": "byteflow.tools 소개", diff --git a/src/core/i18n/translations/zh-CN.json b/src/core/i18n/translations/zh-CN.json index 1cf3620b..dd8eb154 100644 --- a/src/core/i18n/translations/zh-CN.json +++ b/src/core/i18n/translations/zh-CN.json @@ -30,7 +30,15 @@ "text_content": "文本与内容", "web_api": "Web 与 API", "generators_ids": "生成器与 ID", - "design_media": "设计与媒体" + "design_media": "设计与媒体", + "data_code_formats": "数据与代码格式", + "encoding_crypto": "编码与加密", + "web_api_network": "Web、API 与网络", + "devops_logs": "DevOps 与日志", + "text_regex": "文本与正则", + "images_svg_css": "图片、SVG 与 CSS", + "generators_calculators": "生成器与计算器", + "social_metadata": "社交与元数据" }, "features": { "privacy_title": "100% 隐私设计", @@ -2544,7 +2552,15 @@ "text_content_desc": "提供文本统计、空白清理、大小写转换、差异对比等内容处理工具。", "web_api_desc": "围绕 JWT、哈希、请求头、HTTP、OpenAPI 与请求构建进行 Web/API 调试。", "generators_ids_desc": "生成 ID、密码、时间戳、密钥与测试开发常用数据。", - "design_media_desc": "生成代码图片、颜色结果、SVG 资产等可分享的设计与媒体内容。" + "design_media_desc": "生成代码图片、颜色结果、SVG 资产等可分享的设计与媒体内容。", + "data_code_formats_desc": "格式化、检查并转换 JSON、YAML、XML、CSV、Schema 与代码相关数据格式。", + "encoding_crypto_desc": "在本地完成编码、解码、哈希、令牌检查以及证书和密钥材料处理。", + "web_api_network_desc": "构建、检查并调试 HTTP、OpenAPI、URL、User-Agent、请求头、robots 规则与网络数据。", + "devops_logs_desc": "解析环境文件、清理日志、检查 HAR、构建 cron 表达式并准备运维片段。", + "text_regex_desc": "清理、比较、统计、转换并测试文本、Unicode、slug、正则表达式与 Markdown 内容。", + "images_svg_css_desc": "编辑图片、提取颜色、优化 SVG、生成 CSS 视觉效果并创建本地设计资产。", + "generators_calculators_desc": "生成 ID、密码、时间戳、二维码、条码、随机列表与测试数据。", + "social_metadata_desc": "创建 Open Graph 元数据、社交预览、缩略图和可分享的社交媒体素材。" }, "pages": { "about_title": "关于 byteflow.tools", diff --git a/src/core/i18n/translations/zh-TW.json b/src/core/i18n/translations/zh-TW.json index 5b7ab9d6..e9085bf5 100644 --- a/src/core/i18n/translations/zh-TW.json +++ b/src/core/i18n/translations/zh-TW.json @@ -30,7 +30,15 @@ "text_content": "文字與內容", "web_api": "Web 與 API", "generators_ids": "產生器與 ID", - "design_media": "設計與媒體" + "design_media": "設計與媒體", + "data_code_formats": "資料與程式碼格式", + "encoding_crypto": "編碼與加密", + "web_api_network": "Web、API 與網路", + "devops_logs": "DevOps 與日誌", + "text_regex": "文字與正則", + "images_svg_css": "圖片、SVG 與 CSS", + "generators_calculators": "產生器與計算器", + "social_metadata": "社群與中繼資料" }, "features": { "privacy_title": "100% 隱私設計", @@ -2544,7 +2552,15 @@ "text_content_desc": "提供文字統計、空白清理、大小寫轉換、差異比對等內容處理工具。", "web_api_desc": "聚焦 JWT、雜湊、標頭、HTTP、OpenAPI 與請求建構的 Web/API 除錯。", "generators_ids_desc": "產生 ID、密碼、時間戳、密鑰與開發測試常用資料。", - "design_media_desc": "產生程式碼圖片、色彩輸出、SVG 資產等可分享的設計與媒體內容。" + "design_media_desc": "產生程式碼圖片、色彩輸出、SVG 資產等可分享的設計與媒體內容。", + "data_code_formats_desc": "格式化、檢查並轉換 JSON、YAML、XML、CSV、Schema 與程式碼相關資料格式。", + "encoding_crypto_desc": "在本機完成編碼、解碼、雜湊、權杖檢查以及憑證和金鑰資料處理。", + "web_api_network_desc": "建構、檢查並除錯 HTTP、OpenAPI、URL、User-Agent、標頭、robots 規則與網路資料。", + "devops_logs_desc": "解析環境檔、清理日誌、檢查 HAR、建立 cron 表達式並準備維運片段。", + "text_regex_desc": "清理、比較、統計、轉換並測試文字、Unicode、slug、正則表達式與 Markdown 內容。", + "images_svg_css_desc": "編輯圖片、擷取色彩、最佳化 SVG、產生 CSS 視覺效果並建立本機設計資產。", + "generators_calculators_desc": "產生 ID、密碼、時間戳、QR 碼、條碼、隨機清單與測試資料。", + "social_metadata_desc": "建立 Open Graph 中繼資料、社群預覽、縮圖和可分享的社群媒體素材。" }, "pages": { "about_title": "關於 byteflow.tools", diff --git a/src/core/registry/menu-groups.ts b/src/core/registry/menu-groups.ts index 73d41f04..09231d98 100644 --- a/src/core/registry/menu-groups.ts +++ b/src/core/registry/menu-groups.ts @@ -1,6 +1,16 @@ import { TOOL_REGISTRY, type ToolMeta } from "@/core/registry" -export type MenuGroupKey = +export type PrimaryMenuGroupKey = + | "data_code_formats" + | "encoding_crypto" + | "web_api_network" + | "devops_logs" + | "text_regex" + | "images_svg_css" + | "generators_calculators" + | "social_metadata" + +export type LegacyMenuGroupKey = | "format_validate" | "convert_encode" | "text_content" @@ -8,6 +18,8 @@ export type MenuGroupKey = | "generators_ids" | "design_media" +export type MenuGroupKey = PrimaryMenuGroupKey | LegacyMenuGroupKey + export type MenuGroupDef = { key: MenuGroupKey navKey: MenuGroupKey @@ -15,11 +27,34 @@ export type MenuGroupDef = { descriptionKey: `${MenuGroupKey}_desc` } +export type PrimaryMenuGroupDef = MenuGroupDef & { + key: PrimaryMenuGroupKey + navKey: PrimaryMenuGroupKey + descriptionKey: `${PrimaryMenuGroupKey}_desc` +} + +export type LegacyMenuGroupDef = MenuGroupDef & { + key: LegacyMenuGroupKey + navKey: LegacyMenuGroupKey + descriptionKey: `${LegacyMenuGroupKey}_desc` +} + export type MenuGroup = MenuGroupDef & { items: ToolMeta[] } -export const MENU_GROUP_DEFS: MenuGroupDef[] = [ +export const MENU_GROUP_DEFS: PrimaryMenuGroupDef[] = [ + { key: "data_code_formats", navKey: "data_code_formats", slug: "data-code-formats", descriptionKey: "data_code_formats_desc" }, + { key: "encoding_crypto", navKey: "encoding_crypto", slug: "encoding-crypto", descriptionKey: "encoding_crypto_desc" }, + { key: "web_api_network", navKey: "web_api_network", slug: "web-api-network", descriptionKey: "web_api_network_desc" }, + { key: "devops_logs", navKey: "devops_logs", slug: "devops-logs", descriptionKey: "devops_logs_desc" }, + { key: "text_regex", navKey: "text_regex", slug: "text-regex", descriptionKey: "text_regex_desc" }, + { key: "images_svg_css", navKey: "images_svg_css", slug: "images-svg-css", descriptionKey: "images_svg_css_desc" }, + { key: "generators_calculators", navKey: "generators_calculators", slug: "generators-calculators", descriptionKey: "generators_calculators_desc" }, + { key: "social_metadata", navKey: "social_metadata", slug: "social-metadata", descriptionKey: "social_metadata_desc" }, +] + +export const LEGACY_MENU_GROUP_DEFS: LegacyMenuGroupDef[] = [ { key: "format_validate", navKey: "format_validate", slug: "format-validate", descriptionKey: "format_validate_desc" }, { key: "convert_encode", navKey: "convert_encode", slug: "convert-encode", descriptionKey: "convert_encode_desc" }, { key: "text_content", navKey: "text_content", slug: "text-content", descriptionKey: "text_content_desc" }, @@ -28,7 +63,22 @@ export const MENU_GROUP_DEFS: MenuGroupDef[] = [ { key: "design_media", navKey: "design_media", slug: "design-media", descriptionKey: "design_media_desc" }, ] -const OVERRIDE_GROUP_BY_TOOL_KEY: Record = { +const PRIMARY_GROUP_BY_FAMILY: Record, PrimaryMenuGroupKey> = { + "data-formats": "data_code_formats", + "formatters-validators": "data_code_formats", + "encoders-decoders": "encoding_crypto", + "security-tokens": "encoding_crypto", + "network-http": "web_api_network", + "devops-logs": "devops_logs", + "text-strings": "text_regex", + "images-media": "images_svg_css", + "svg-css-visual": "images_svg_css", + generators: "generators_calculators", + "workbench-pipeline": "devops_logs", + "social-metadata": "social_metadata", +} + +const LEGACY_OVERRIDE_GROUP_BY_TOOL_KEY: Record = { base64_encode_decode: "convert_encode", url_encode_decode: "convert_encode", image_base64: "convert_encode", @@ -97,8 +147,19 @@ const OVERRIDE_GROUP_BY_TOOL_KEY: Record = { react_native_shadow_generator: "design_media", } -function classifyToolToMenuGroup(tool: ToolMeta): MenuGroupKey { - const overridden = OVERRIDE_GROUP_BY_TOOL_KEY[tool.key] +function classifyToolToPrimaryMenuGroup(tool: ToolMeta): PrimaryMenuGroupKey { + if (tool.family && PRIMARY_GROUP_BY_FAMILY[tool.family]) { + return PRIMARY_GROUP_BY_FAMILY[tool.family] + } + + if (tool.category === "network-web") return "web_api_network" + if (tool.category === "formatters") return "data_code_formats" + if (tool.category === "generators") return "generators_calculators" + return "text_regex" +} + +function classifyToolToLegacyMenuGroup(tool: ToolMeta): LegacyMenuGroupKey { + const overridden = LEGACY_OVERRIDE_GROUP_BY_TOOL_KEY[tool.key] if (overridden) return overridden if (tool.category === "network-web") return "web_api" @@ -107,19 +168,31 @@ function classifyToolToMenuGroup(tool: ToolMeta): MenuGroupKey { return "text_content" } -export function getMenuGroups(): MenuGroup[] { - return MENU_GROUP_DEFS.map((group) => ({ +function buildGroups(defs: MenuGroupDef[], classifier: (tool: ToolMeta) => MenuGroupKey): MenuGroup[] { + return defs.map((group) => ({ ...group, - items: TOOL_REGISTRY.filter((tool) => classifyToolToMenuGroup(tool) === group.key), + items: TOOL_REGISTRY.filter((tool) => classifier(tool) === group.key), })) } +export function getMenuGroups(): MenuGroup[] { + return buildGroups(MENU_GROUP_DEFS, classifyToolToPrimaryMenuGroup) +} + +export function getAllMenuGroups(): MenuGroup[] { + return [ + ...getMenuGroups(), + ...buildGroups(LEGACY_MENU_GROUP_DEFS, classifyToolToLegacyMenuGroup), + ] +} + export function getMenuGroupByKey(key: MenuGroupKey): MenuGroup | undefined { - return getMenuGroups().find((group) => group.key === key) + return getAllMenuGroups().find((group) => group.key === key) } export function getMenuGroupBySlug(slug: string): MenuGroup | undefined { - return getMenuGroups().find((group) => group.slug === slug) + return getAllMenuGroups().find((group) => group.slug === slug) } export const MENU_GROUP_HUB_SLUGS = MENU_GROUP_DEFS.map((group) => group.slug) +export const LEGACY_MENU_GROUP_HUB_SLUGS = LEGACY_MENU_GROUP_DEFS.map((group) => group.slug) diff --git a/src/core/registry/tool-taxonomy.ts b/src/core/registry/tool-taxonomy.ts index 51c99b8b..ac93ab99 100644 --- a/src/core/registry/tool-taxonomy.ts +++ b/src/core/registry/tool-taxonomy.ts @@ -68,6 +68,18 @@ const FAMILY_BY_TOOL_KEY: Partial> = { color_shades_generator: "svg-css-visual", cron_visualizer: "devops-logs", crontab_generator: "devops-logs", + css_background_pattern_generator: "svg-css-visual", + css_border_radius_generator: "svg-css-visual", + css_box_shadow_generator: "svg-css-visual", + css_checkbox_generator: "svg-css-visual", + css_clip_path_generator: "svg-css-visual", + css_cubic_bezier_generator: "svg-css-visual", + css_glassmorphism_generator: "svg-css-visual", + css_gradient_generator: "svg-css-visual", + css_loader_generator: "svg-css-visual", + css_switch_generator: "svg-css-visual", + css_text_glitch_effect_generator: "svg-css-visual", + css_triangle_generator: "svg-css-visual", csp_parser: "security-tokens", csv_diff: "data-formats", csv_json_converter: "data-formats", @@ -239,4 +251,3 @@ export function getToolTaxonomy(tool: ToolMeta): ToolTaxonomy { capabilities: uniqueSorted(capabilities), } } - diff --git a/src/core/routing/route-context.ts b/src/core/routing/route-context.ts index 23134e4c..072e7fa0 100644 --- a/src/core/routing/route-context.ts +++ b/src/core/routing/route-context.ts @@ -4,7 +4,19 @@ import { CLIENT_MENU_GROUPS, getClientToolBySlug } from "@/generated/client-tool export type RouteType = "home" | "tool" | "hub" | "content" | "other" const CATEGORY_HUB_SLUGS = ["formatters", "text-tools", "generators", "network-tools"] as const -const HUB_SLUGS = new Set([...CATEGORY_HUB_SLUGS, ...CLIENT_MENU_GROUPS.map((group) => group.hubSlug)]) +const LEGACY_MENU_GROUP_HUB_SLUGS = [ + "format-validate", + "convert-encode", + "text-content", + "web-api", + "generators-ids", + "design-media", +] as const +const HUB_SLUGS = new Set([ + ...CATEGORY_HUB_SLUGS, + ...CLIENT_MENU_GROUPS.map((group) => group.hubSlug), + ...LEGACY_MENU_GROUP_HUB_SLUGS, +]) const CONTENT_ROUTE_SLUGS = new Set(["about", "pricing", "contact", "privacy", "terms"]) export function getRouteContext(pathname: string): { diff --git a/src/generated/client-tool-lookup.ts b/src/generated/client-tool-lookup.ts index da55ddb0..e2386deb 100644 --- a/src/generated/client-tool-lookup.ts +++ b/src/generated/client-tool-lookup.ts @@ -2045,14 +2045,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_border_radius_generator": { @@ -2073,14 +2074,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_box_shadow_generator": { @@ -2101,15 +2103,16 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators", - "hash" + "hash", + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_checkbox_generator": { @@ -2130,14 +2133,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_clip_path_generator": { @@ -2158,15 +2162,16 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators", - "hash" + "hash", + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_cubic_bezier_generator": { @@ -2187,14 +2192,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_glassmorphism_generator": { @@ -2215,14 +2221,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_gradient_generator": { @@ -2243,15 +2250,16 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "color", "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_loader_generator": { @@ -2272,14 +2280,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_switch_generator": { @@ -2300,14 +2309,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_text_glitch_effect_generator": { @@ -2328,14 +2338,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "css_triangle_generator": { @@ -2356,14 +2367,15 @@ const CLIENT_TOOL_LOOKUP: Record = { ], "networkAccess": "none", "persistInput": null, - "family": "generators", + "family": "svg-css-visual", "tags": [ "css", - "generators" + "svg-css-visual" ], "capabilities": [ "browser-local", - "offline-capable" + "offline-capable", + "visual-output" ] }, "qr_code_generator": { @@ -3834,9 +3846,9 @@ const CLIENT_TOOL_KEY_BY_SLUG: Record = { export const CLIENT_MENU_GROUPS: ReadonlyArray = [ { - "key": "format_validate", - "navKey": "format_validate", - "hubSlug": "format-validate", + "key": "data_code_formats", + "navKey": "data_code_formats", + "hubSlug": "data-code-formats", "items": [ { "key": "json_formatter", @@ -3871,8 +3883,12 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "slug": "html-formatter" }, { - "key": "html_to_markdown", - "slug": "html-to-markdown" + "key": "yaml_json_converter", + "slug": "yaml-json-converter" + }, + { + "key": "json_to_typescript", + "slug": "json-to-typescript" }, { "key": "css_minifier", @@ -3886,6 +3902,10 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "key": "json_diff_viewer", "slug": "json-diff-viewer" }, + { + "key": "csv_json_converter", + "slug": "csv-json-converter" + }, { "key": "ndjson_formatter", "slug": "ndjson-formatter" @@ -3894,10 +3914,18 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "key": "csv_diff", "slug": "csv-diff" }, + { + "key": "jq_playground", + "slug": "jq-playground" + }, { "key": "yaml_merge_patch_explorer", "slug": "yaml-merge-patch-explorer" }, + { + "key": "yq_playground", + "slug": "yq-playground" + }, { "key": "structured_data_visualizer", "slug": "structured-data-visualizer" @@ -3905,26 +3933,14 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ ] }, { - "key": "convert_encode", - "navKey": "convert_encode", - "hubSlug": "convert-encode", + "key": "encoding_crypto", + "navKey": "encoding_crypto", + "hubSlug": "encoding-crypto", "items": [ { "key": "html_encoder_decoder", "slug": "html-encoder-decoder" }, - { - "key": "yaml_json_converter", - "slug": "yaml-json-converter" - }, - { - "key": "json_to_typescript", - "slug": "json-to-typescript" - }, - { - "key": "csv_json_converter", - "slug": "csv-json-converter" - }, { "key": "base64_encode_decode", "slug": "base64-encode-decode" @@ -3934,119 +3950,80 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "slug": "url-encode-decode" }, { - "key": "image_base64", - "slug": "image-base64" + "key": "jwt_decoder", + "slug": "jwt-decoder" }, { - "key": "gzip_brotli_lab", - "slug": "gzip-brotli-lab" + "key": "jwt_workbench", + "slug": "jwt-workbench" }, { - "key": "hex_bytes_workbench", - "slug": "hex-bytes-workbench" - } - ] - }, - { - "key": "text_content", - "navKey": "text_content", - "hubSlug": "text-content", - "items": [ + "key": "hash_generator", + "slug": "hash-generator" + }, { - "key": "markdown_preview", - "slug": "markdown-preview" + "key": "md5_generator", + "slug": "md5-generator" }, { - "key": "text_diff_checker", - "slug": "text-diff-checker" + "key": "image_base64", + "slug": "image-base64" }, { - "key": "multiple_whitespace_remover", - "slug": "multiple-whitespace-remover" + "key": "certificate_decoder", + "slug": "certificate-decoder" }, { - "key": "letter_counter", - "slug": "letter-counter" + "key": "jwt_verifier", + "slug": "jwt-verifier" }, { - "key": "bionic_reading_converter", - "slug": "bionic-reading-converter" + "key": "csp_parser", + "slug": "csp-parser" }, { - "key": "lorem_ipsum", - "slug": "lorem-ipsum" + "key": "security_header_analyzer", + "slug": "security-header-analyzer" }, { - "key": "ascii_art_generator", - "slug": "ascii-art-generator" + "key": "totp_generator", + "slug": "totp-generator" }, { - "key": "slugify_case_converter", - "slug": "slugify-case-converter" + "key": "gzip_brotli_lab", + "slug": "gzip-brotli-lab" }, { - "key": "invisible_chars_detector", - "slug": "invisible-characters-detector" + "key": "saml_decoder", + "slug": "saml-decoder" }, { - "key": "unicode_inspector", - "slug": "unicode-inspector" + "key": "asn1_der_inspector", + "slug": "asn1-der-inspector" + }, + { + "key": "hex_bytes_workbench", + "slug": "hex-bytes-workbench" } ] }, { - "key": "web_api", - "navKey": "web_api", - "hubSlug": "web-api", + "key": "web_api_network", + "navKey": "web_api_network", + "hubSlug": "web-api-network", "items": [ { "key": "openapi_viewer", "slug": "openapi-viewer" }, - { - "key": "jwt_decoder", - "slug": "jwt-decoder" - }, - { - "key": "jwt_workbench", - "slug": "jwt-workbench" - }, - { - "key": "hash_generator", - "slug": "hash-generator" - }, - { - "key": "md5_generator", - "slug": "md5-generator" - }, - { - "key": "regex_tester", - "slug": "regex-tester" - }, - { - "key": "regex_generator", - "slug": "regex-generator" - }, - { - "key": "crontab_generator", - "slug": "crontab-generator" - }, { "key": "user_agent_parser", "slug": "user-agent-parser" }, - { - "key": "cron_visualizer", - "slug": "cron-visualizer" - }, { "key": "http_status_codes", "slug": "http-status-codes" }, - { - "key": "chmod_calculator", - "slug": "chmod-calculator" - }, { "key": "cidr_subnet_calculator", "slug": "cidr-subnet-calculator" @@ -4055,10 +4032,6 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "key": "url_parser", "slug": "url-parser" }, - { - "key": "certificate_decoder", - "slug": "certificate-decoder" - }, { "key": "http_request_builder", "slug": "http-request-builder" @@ -4067,115 +4040,118 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "key": "curl_to_code", "slug": "curl-to-code" }, - { - "key": "jwt_verifier", - "slug": "jwt-verifier" - }, { "key": "robots_txt_tester", "slug": "robots-txt-tester" }, - { - "key": "csp_parser", - "slug": "csp-parser" - }, { "key": "header_diff", "slug": "header-diff" }, - { - "key": "security_header_analyzer", - "slug": "security-header-analyzer" - }, { "key": "openapi_mock", "slug": "openapi-mock" }, { - "key": "docker_run_to_compose", - "slug": "docker-run-to-compose" - }, + "key": "har_viewer_sanitizer", + "slug": "har-viewer-sanitizer" + } + ] + }, + { + "key": "devops_logs", + "navKey": "devops_logs", + "hubSlug": "devops-logs", + "items": [ { - "key": "local_log_parser", - "slug": "local-log-parser" + "key": "env_parser", + "slug": "env-parser" }, { - "key": "jq_playground", - "slug": "jq-playground" + "key": "crontab_generator", + "slug": "crontab-generator" }, { - "key": "log_scrubber", - "slug": "log-scrubber" + "key": "cron_visualizer", + "slug": "cron-visualizer" }, { - "key": "yq_playground", - "slug": "yq-playground" + "key": "chmod_calculator", + "slug": "chmod-calculator" }, { - "key": "har_viewer_sanitizer", - "slug": "har-viewer-sanitizer" + "key": "docker_run_to_compose", + "slug": "docker-run-to-compose" }, { - "key": "pipeline_builder", - "slug": "pipeline-builder" + "key": "local_log_parser", + "slug": "local-log-parser" }, { - "key": "saml_decoder", - "slug": "saml-decoder" + "key": "log_scrubber", + "slug": "log-scrubber" }, { - "key": "asn1_der_inspector", - "slug": "asn1-der-inspector" + "key": "pipeline_builder", + "slug": "pipeline-builder" } ] }, { - "key": "generators_ids", - "navKey": "generators_ids", - "hubSlug": "generators-ids", + "key": "text_regex", + "navKey": "text_regex", + "hubSlug": "text-regex", "items": [ { - "key": "unix_timestamp", - "slug": "unix-timestamp" + "key": "markdown_preview", + "slug": "markdown-preview" }, { - "key": "uuid_generator", - "slug": "uuid-generator" + "key": "html_to_markdown", + "slug": "html-to-markdown" }, { - "key": "password_generator", - "slug": "password-generator" + "key": "text_diff_checker", + "slug": "text-diff-checker" }, { - "key": "barcode_generator", - "slug": "barcode-generator" + "key": "multiple_whitespace_remover", + "slug": "multiple-whitespace-remover" }, { - "key": "fake_iban_generator", - "slug": "fake-iban-generator" + "key": "letter_counter", + "slug": "letter-counter" }, { - "key": "list_randomizer", - "slug": "list-randomizer" + "key": "bionic_reading_converter", + "slug": "bionic-reading-converter" }, { - "key": "env_parser", - "slug": "env-parser" + "key": "regex_tester", + "slug": "regex-tester" }, { - "key": "id_generator", - "slug": "id-generator" + "key": "regex_generator", + "slug": "regex-generator" }, { - "key": "totp_generator", - "slug": "totp-generator" + "key": "slugify_case_converter", + "slug": "slugify-case-converter" + }, + { + "key": "invisible_chars_detector", + "slug": "invisible-characters-detector" + }, + { + "key": "unicode_inspector", + "slug": "unicode-inspector" } ] }, { - "key": "design_media", - "navKey": "design_media", - "hubSlug": "design-media", + "key": "images_svg_css", + "navKey": "images_svg_css", + "hubSlug": "images-svg-css", "items": [ { "key": "svg_optimizer", @@ -4237,46 +4213,6 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ "key": "image_filters", "slug": "image-filters" }, - { - "key": "instagram_filters", - "slug": "instagram-filters" - }, - { - "key": "instagram_post_generator", - "slug": "instagram-post-generator" - }, - { - "key": "instagram_story_generator", - "slug": "instagram-story-generator" - }, - { - "key": "open_graph_meta_generator", - "slug": "open-graph-meta-generator" - }, - { - "key": "tweet_generator", - "slug": "tweet-generator" - }, - { - "key": "tweet_to_image_converter", - "slug": "tweet-to-image-converter" - }, - { - "key": "twitter_ad_revenue_generator", - "slug": "twitter-ad-revenue-generator" - }, - { - "key": "instagram_photo_downloader", - "slug": "instagram-photo-downloader" - }, - { - "key": "vimeo_thumbnail_grabber", - "slug": "vimeo-thumbnail-grabber" - }, - { - "key": "youtube_thumbnail_grabber", - "slug": "youtube-thumbnail-grabber" - }, { "key": "image_resizer", "slug": "image-resizer" @@ -4352,10 +4288,100 @@ export const CLIENT_MENU_GROUPS: ReadonlyArray = [ { "key": "css_triangle_generator", "slug": "css-triangle-generator" + } + ] + }, + { + "key": "generators_calculators", + "navKey": "generators_calculators", + "hubSlug": "generators-calculators", + "items": [ + { + "key": "unix_timestamp", + "slug": "unix-timestamp" + }, + { + "key": "uuid_generator", + "slug": "uuid-generator" + }, + { + "key": "lorem_ipsum", + "slug": "lorem-ipsum" + }, + { + "key": "password_generator", + "slug": "password-generator" }, { "key": "qr_code_generator", "slug": "qr-code-generator" + }, + { + "key": "barcode_generator", + "slug": "barcode-generator" + }, + { + "key": "fake_iban_generator", + "slug": "fake-iban-generator" + }, + { + "key": "list_randomizer", + "slug": "list-randomizer" + }, + { + "key": "ascii_art_generator", + "slug": "ascii-art-generator" + }, + { + "key": "id_generator", + "slug": "id-generator" + } + ] + }, + { + "key": "social_metadata", + "navKey": "social_metadata", + "hubSlug": "social-metadata", + "items": [ + { + "key": "instagram_filters", + "slug": "instagram-filters" + }, + { + "key": "instagram_post_generator", + "slug": "instagram-post-generator" + }, + { + "key": "instagram_story_generator", + "slug": "instagram-story-generator" + }, + { + "key": "open_graph_meta_generator", + "slug": "open-graph-meta-generator" + }, + { + "key": "tweet_generator", + "slug": "tweet-generator" + }, + { + "key": "tweet_to_image_converter", + "slug": "tweet-to-image-converter" + }, + { + "key": "twitter_ad_revenue_generator", + "slug": "twitter-ad-revenue-generator" + }, + { + "key": "instagram_photo_downloader", + "slug": "instagram-photo-downloader" + }, + { + "key": "vimeo_thumbnail_grabber", + "slug": "vimeo-thumbnail-grabber" + }, + { + "key": "youtube_thumbnail_grabber", + "slug": "youtube-thumbnail-grabber" } ] } diff --git a/src/generated/tool-index.json b/src/generated/tool-index.json index d388ed57..6e388f63 100644 --- a/src/generated/tool-index.json +++ b/src/generated/tool-index.json @@ -1,9 +1,9 @@ { - "generatedAt": "2026-06-17T14:58:43.146Z", + "generatedAt": "2026-06-18T17:14:32.292Z", "counts": { "canonicalTools": 121, "aliasRoutes": 1, - "routeDirs": 170, + "routeDirs": 178, "unknownRouteDirs": 0 }, "canonicalTools": [ @@ -3033,6 +3033,14 @@ "inHubGroup": false, "inStaticGroup": false }, + { + "slug": "data-code-formats", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "design-media", "hasPage": true, @@ -3041,6 +3049,14 @@ "inHubGroup": true, "inStaticGroup": false }, + { + "slug": "devops-logs", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "dns-records-uptime", "hasPage": true, @@ -3057,6 +3073,14 @@ "inHubGroup": false, "inStaticGroup": false }, + { + "slug": "encoding-crypto", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "env-parser", "hasPage": true, @@ -3097,6 +3121,14 @@ "inHubGroup": true, "inStaticGroup": false }, + { + "slug": "generators-calculators", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "generators-ids", "hasPage": true, @@ -3305,6 +3337,14 @@ "inHubGroup": false, "inStaticGroup": false }, + { + "slug": "images-svg-css", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "instagram-filters", "hasPage": true, @@ -3761,6 +3801,14 @@ "inHubGroup": false, "inStaticGroup": false }, + { + "slug": "social-metadata", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "sql-formatter", "hasPage": true, @@ -3849,6 +3897,14 @@ "inHubGroup": false, "inStaticGroup": false }, + { + "slug": "text-regex", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "text-to-handwriting-converter", "hasPage": true, @@ -3977,6 +4033,14 @@ "inHubGroup": true, "inStaticGroup": false }, + { + "slug": "web-api-network", + "hasPage": true, + "kind": "hub", + "targetSlug": null, + "inHubGroup": true, + "inStaticGroup": false + }, { "slug": "xml-formatter", "hasPage": true, diff --git a/src/lib/ia-stability-baseline.json b/src/lib/ia-stability-baseline.json index 6a4dfebd..aabc75de 100644 --- a/src/lib/ia-stability-baseline.json +++ b/src/lib/ia-stability-baseline.json @@ -1,12 +1,14 @@ { "schemaVersion": 1, "menuGroupHubs": [ - "convert-encode", - "design-media", - "format-validate", - "generators-ids", - "text-content", - "web-api" + "data-code-formats", + "devops-logs", + "encoding-crypto", + "generators-calculators", + "images-svg-css", + "social-metadata", + "text-regex", + "web-api-network" ], "toolCategoryHubs": [ "formatters", @@ -16,15 +18,23 @@ ], "sitemapHubs": [ "convert-encode", + "data-code-formats", "design-media", + "devops-logs", + "encoding-crypto", "format-validate", "formatters", "generators", + "generators-calculators", "generators-ids", + "images-svg-css", "network-tools", + "social-metadata", "text-content", + "text-regex", "text-tools", - "web-api" + "web-api", + "web-api-network" ], "requiredPersistentPages": [ "about", diff --git a/src/lib/sitemap-lastmod.json b/src/lib/sitemap-lastmod.json index e614b574..e5f26d4d 100644 --- a/src/lib/sitemap-lastmod.json +++ b/src/lib/sitemap-lastmod.json @@ -10,13 +10,13 @@ "fr" ], "home": { - "en": "2026-06-14T00:00:00.000Z", - "zh-CN": "2026-06-14T00:00:00.000Z", - "zh-TW": "2026-06-14T00:00:00.000Z", - "ja": "2026-06-14T00:00:00.000Z", - "ko": "2026-06-14T00:00:00.000Z", - "de": "2026-06-14T00:00:00.000Z", - "fr": "2026-06-14T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "hubs": { "formatters": { @@ -55,6 +55,78 @@ "de": "2026-06-14T00:00:00.000Z", "fr": "2026-06-14T00:00:00.000Z" }, + "data-code-formats": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "encoding-crypto": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "web-api-network": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "devops-logs": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "text-regex": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "images-svg-css": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "generators-calculators": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, + "social-metadata": { + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" + }, "format-validate": { "en": "2026-06-14T00:00:00.000Z", "zh-CN": "2026-06-14T00:00:00.000Z", diff --git a/src/lib/sitemap-route-groups.json b/src/lib/sitemap-route-groups.json index ecae67a0..9403097f 100644 --- a/src/lib/sitemap-route-groups.json +++ b/src/lib/sitemap-route-groups.json @@ -4,6 +4,14 @@ "text-tools", "generators", "network-tools", + "data-code-formats", + "encoding-crypto", + "web-api-network", + "devops-logs", + "text-regex", + "images-svg-css", + "generators-calculators", + "social-metadata", "format-validate", "convert-encode", "text-content", diff --git a/tests/component/layout-components.test.tsx b/tests/component/layout-components.test.tsx index 1cd0ec0b..6efddf22 100644 --- a/tests/component/layout-components.test.tsx +++ b/tests/component/layout-components.test.tsx @@ -201,7 +201,7 @@ describe("layout components", () => { render(