From 2cc3c1728b3a168b43223aa8d9f016884e0377ff Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Wed, 28 Oct 2026 12:38:04 +0100 Subject: [PATCH] docs(33): UI design contract --- .../33-UI-SPEC.md | 1381 +++++++++++++++++ 1 file changed, 1381 insertions(+) create mode 100644 .planning/phases/33-seo-output-audit-and-content/33-UI-SPEC.md diff --git a/.planning/phases/33-seo-output-audit-and-content/33-UI-SPEC.md b/.planning/phases/33-seo-output-audit-and-content/33-UI-SPEC.md new file mode 100644 index 00000000..d24a0e71 --- /dev/null +++ b/.planning/phases/33-seo-output-audit-and-content/33-UI-SPEC.md @@ -0,0 +1,1381 @@ +--- +phase: "33" +slug: "seo-output-audit-and-content" +status: approved +reviewed_at: "2026-10-28" +shadcn_initialized: false +preset: none +created: "2026-10-28" +--- + +# Phase 33 — UI Design Contract + +> Visual and interaction contract for frontend phases. Generated by msd-ui-researcher, verified by msd-ui-checker. + +Phase 33 has two UI surfaces, and this contract covers both: + +1. **Admin.** These are new screens and fields in `sm-seo-plugin`, `sm-sitemap-plugin` and `sm-journal-plugin`, rendered through the generic cabana/boardwalk mechanisms. They follow the approved Phase 32 conventions (`32-UI-SPEC.md`: Direction C, E-elements, P-partials, C-contracts, en/pl copy) **without reinventing any of them**. Numbering continues from Phase 32: elements E8–E10, partials P6–P9, contracts C15 onward. The framework (`summercms.go`) gains no SEO-named UI, phrase, icon or component (D-90), and the SPA in `admin/` is not changed. +2. **Public Nuxt layers.** The new `vue-seo-layer` (name to confirm, D-97) plus changes in `vue-journal-layer` and `vue-faq-layer`. These components are framework-wide: many sites consume them, each with its own theme. The contract fixes their **structure, semantics, accessibility, behaviour and copy**. It sets only neutral, token-driven default styles that a site overrides, following the layer token convention (`journal-tokens.css`, `menu-tokens.css`). It contains no site-specific shaping. + +The contract fixes eight things: + +1. Where the new admin screens live: four new SEO side-menu items, new SEO settings tabs, new SEO-tab fields, the rewritten sitemap settings screen, and a new Journal settings tab. +2. What the YAML declares: fields, tabs, columns, filters, toolbar, record and bulk actions. +3. Three new SEO plugin elements (E8 schema fields, E9 graph preview, E10 internal links). Each states why no built-in type is enough. +4. Four Blog Editor additions in `sm-journal-plugin` (J1–J4): heading levels by setting, the outline panel, link rel and the generic-anchor warning, and image file name and decorative images. +5. New server-rendered partials (P6–P9, S1–S2), using the existing kit only (`.summer-callout*`, `.summer-table*`, `.summer-stats`, `.summer-markdown`). +6. Public layer components (L1–L8): breadcrumbs, pagination, the HTML sitemap page and footer link, post cards, the heading convention, body-content rendering rules, and the layer label dictionary and tokens. +7. All copy in English and Polish. +8. MCP parity: every admin operation listed here maps to an MCP tool (D-10). The contract adds no UI-only operation. + +**Sources:** +- Decisions (locked): 33-CONTEXT.md, including the Journal H1 setting (Jakub, 2026-10-28, amends D-50/D-51), and the full decision text in 32-CONTEXT.md (D-03, D-10, D-14, D-24, D-25, D-30..D-34, D-40..D-46, D-50..D-52, D-55..D-57, D-70, D-71, D-80..D-86, D-95..D-97). +- Phase 32 UI contract: 32-UI-SPEC.md (approved 2026-10-26). +- Research: 33-RESEARCH.md (stack, resolve-answer fields, crawler pattern, Pitfalls 2, 3, 6, 7 and 11, Open Questions 4, 8 and 9). +- Code scan, 2026-10-28: + - `sm-seo-plugin`: `admin_navigation.go`, `extensions.go` `seoTabYAML`, `models/settings/fields.yaml`, `assets/src/elements/*`, `classes/issues.go`, `lang/*`; + - `sm-sitemap-plugin`: `admin.go`, `models/settings/fields.yaml`, `lang/*`; + - `sm-journal-plugin`: `assets/src/blogeditor/{element,toolbar,labels}.ts`, `blogeditor.css`, `models/settings/fields.yaml`, `lang/*`; + - `summercms.go`: `modules/pact/capabilities.go` (`AdminAction`, `AdminBulkAction`, `AdminRecordAction`, `FormFieldType`, `FormFieldDecoded.Notices`, `RowState`), `modules/cabana/{field_trigger.go,list_schema.go,field_taglist.go}`, `admin/src/views/PreviewView.vue`, `admin/src/styles/main.css`; + - `vue-journal-layer`: `PostCard.vue`, `Pagination.vue`, `RelatedPosts.vue`, `CategoryHeader.vue`, `IndexHeader.vue`, `PostPage.vue`, `utils/pagination.ts`, `assets/css/*`, `i18n/locales/*`; `vue-menu-layer/app/assets/css/menu-tokens.css`; `vue-faq-layer/app/components/Faq/*`; + - app CSS overrides of `.journal-post-card*` in grzybyfunkcjonalne and demo. + +--- + +## Design System + +| Property | Admin (SPA + plugin elements) | Public Nuxt layers | +|----------|-------------------------------|--------------------| +| Tool | none: Direction C, Vue 3 SPA. The shadcn gate does not apply, the same ruling as Phases 10.1, 12.1, 16.3, 19.6.1 and 32 | none: Nuxt 4 layers with plain scoped markup, no component library. The shadcn gate does not apply (Vue, not React) | +| Preset | not applicable | not applicable | +| Component library | Reka UI 2.9.10 primitives and the local `admin/src/components/` (unchanged). The new SEO elements E8–E10 join the framework-free TypeScript custom-element bundle of `sm-seo-plugin/assets/src/elements/` (E1–E7 precedent). J1–J4 extend `golem15-journal-blogeditor` in `sm-journal-plugin/assets/src/blogeditor/` | Layer SFCs: `vue-seo-layer/app/components/Seo/*`, plus changed components in `vue-journal-layer` and `vue-faq-layer` | +| Icon library | SPA: `@lucide/vue` 1.17.0 through the `admin/src/app/icons.ts` registry names. Plugin elements: inline SVG copies of lucide 1.17.0 paths (`circle-check`, `triangle-alert`, `info`, `circle-alert`, `chevron-down`, `external-link`, `copy`), 14px, `stroke="currentColor"`, `aria-hidden="true"`, with the ISC licence line in the bundle header (the E1–E7 rule) | No icons. Separators and gaps are CSS-generated or text characters (`›`, `…`). The site theme may add icons | +| Font | DM Sans 400/600 and DM Mono 400 (unchanged) | Inherited from the site (`font: inherit`). The layer sets no font family | + +--- + +## Component Inventory + +Enumerated by `node --input-type=module -e "import * as r from 'reka-ui'; console.log(Object.keys(r).length)"` (run in `summercms.go/admin`) — 478 exports — reka-ui@2.9.10 — 2026-10-28. +Local SPA components enumerated by `find admin/src/components -name '*.vue' | wc -l` — 62 components — summercms-admin@unversioned (a private workspace package with no `version` field, pinned by repo commit 2ac8663) — 2026-10-28. +Public layer components enumerated by `find app/components -name '*.vue' | wc -l` in each layer, with versions from `git describe --tags` and the resolved Nuxt from `node -p "require('nuxt/package.json').version"` — 11 components in vue-journal-layer@v0.6.0, 3 in vue-faq-layer@v0.4.0, 2 in vue-menu-layer@v0.4.0, nuxt@4.4.8 — 2026-10-28. + +Icon presence was verified on 2026-10-28 by `node --input-type=module -e "import * as l from '@lucide/vue'; for (const n of ['Files','WandSparkles','Gauge','Globe']) console.log(n, n in l)"` (run in `admin/`, @lucide/vue@1.17.0). The registry names `files`, `wand-sparkles`, `gauge` and `globe` are already mapped in `admin/src/app/icons.ts`, so no registry change is needed. + +These tables are **non-exhaustive** lists of known-good components for this phase, not closed allowlists. Checking for another Reka export, local component or layer component is the expected path. + +### Reused unchanged (admin) + +| Component | Import path | Notes | +|-----------|-------------|-------| +| ListView | `admin/src/views/ListView.vue` | Pages, Auto-links, Site audit and Crawls lists. Toolbar actions (`toolbar.buttons` naming a registered `AdminAction`) render as outline buttons, existing | +| FormView | `admin/src/views/FormView.vue` | Pages and Auto-links forms, the extended SEO tab, the sitemap settings singleton | +| PreviewView | `admin/src/views/PreviewView.vue` | Issue and crawl previews. `preview.headerPartial` renders P6 and P7 above the field card; record actions sit in the footer | +| SettingsIndexView / SettingsFormView | `admin/src/views/Settings*.vue` | SEO settings (new tabs), Journal settings (new tab), the sitemap tile | +| FormField / FormGrid / FormTabs | `admin/src/components/form/` | Labels, comments, triggers (`hide` on `checked`/`unchecked`), 422 lines, save notices (`summer-callout--warning` box) | +| PluginField / MLPluginField / WidgetField / PartialField / PartialHost | `admin/src/components/form/fields/`, `admin/src/components/partial/` | Host E8 (field type), E9 and E10 (`type: widget`), J1–J4 (inside the existing Blog Editor field type), P6–P9, S1, S2 | +| Built-in fields: Text, MLText, Textarea, MLTextarea, Number, Switch, Checkbox, Dropdown, Repeater, Datepicker, Taglist | `admin/src/components/form/fields/` | Every new field that needs no custom element | +| RecordActions + ConfirmDialog, BulkActionsMenu, RowStateBadges, FilterBar, DataTable, CellValue | `admin/src/components/form/`, `admin/src/components/list/`, `admin/src/components/ui/` | Cancel crawl, Check again, Ignore / Stop ignoring, bulk Ignore, the "Not on the site", "Off" and "Ignored" badges, scope filters | +| ExtensionFailure, `showToast` | `admin/src/components/ui/ExtensionFailure.vue`, `admin/src/state/useToasts.ts` | Element load failures; action results and refusals | +| Partial kit | `admin/src/styles/main.css` (`.summer-callout`, `--warning`, `--danger`, `__title`, `__text`; `.summer-table`, `.summer-table-wrap`; `.summer-stats`, `.summer-stat__value`, `__label`; `.summer-markdown`) | P6–P9, S1, S2, and inside E9/E10 (light DOM, so the global kit classes apply) | +| SEO elements E1–E7 | `sm-seo-plugin/assets/src/elements/` (`golem15-seo-text`, `-robots`, `-serp`, `-robotstxt`, `-robotstester`, `-transfer`, `-image`) | E7 `seoimage` is reused for the publisher logo (C20) | +| Blog Editor | `sm-journal-plugin/assets/src/blogeditor/` (`golem15-journal-blogeditor`, field types `blogeditor`/`mlblogeditor`) | Extended by J1–J4 (C28) | + +### New admin elements and partials + +| ID | Tag / partial | Registered as | Repo | Why no built-in type is enough | +|----|---------------|---------------|------|-------------------------------| +| E8 | `golem15-seo-schema` | Field type `seoschema` (single value, a JSON **string**). Option `kind: article\|page\|collection\|profile` | sm-seo-plugin | The schema-type choice changes which fields exist (D-44), but cabana triggers only follow `checked`/`unchecked` switches, not dropdown values. The custom JSON-LD needs live parse feedback | +| E9 | `golem15-seo-graph` | `type: widget`, action `seo_graph`, `fill: [seo_schema]` | sm-seo-plugin | Shows the effective `@graph` of the saved page plus the unsaved schema value, and validates it against Google's required and recommended properties (D-46) | +| E10 | `golem15-seo-links` | `type: widget`, action `seo_links`, no fill | sm-seo-plugin | Inbound and outbound link counts from the last crawl and link suggestions (D-82) have no built-in equivalent. It lives in the SEO tab, so Journal stays unaware of SEO (D-96) | +| J1–J4 | additions inside `golem15-journal-blogeditor` | existing `blogeditor`/`mlblogeditor` | sm-journal-plugin | Heading levels by setting, the outline panel, link rel checkboxes with the generic-anchor warning, image file name and decorative images. All of them live in the editor (D-50, D-51, D-81, D-83, D-85) | +| P6 | `crawl_summary` | crawl preview `headerPartial` | sm-seo-plugin | Status callouts, a stats strip and per-kind counts with filter links | +| P7 | `issue_details` | issue preview `headerPartial` | sm-seo-plugin | What was found, how to fix it, and page links | +| P8 | `publisher_preview` | SEO settings, Publisher tab (update) | sm-seo-plugin | The generated publisher node and its findings | +| P9 | `page_status` | Pages form, top (update) | sm-seo-plugin | Report status of a registry page | +| S1 | `sitemap_files` | Sitemap settings, Files tab | sm-sitemap-plugin | Files with URL counts, lastmod, and left-out counts by reason | +| S2 | `indexnow_status` | Sitemap settings, IndexNow tab | sm-sitemap-plugin | Key, key-file URL, last ping | + +The E8–E10 scripts join the bundle that the `mlseotext` field type already lists in `Scripts`. Field-type scripts load on every admin page, so the elements work in the SEO tab injected into other plugins' forms, as E1–E3 do. + +### New and changed public layer components + +| ID | Component | Repo | Notes | +|----|-----------|------|-------| +| L1 | `` | vue-seo-layer | Visible trail from the resolve answer `breadcrumbs`, the same source as `BreadcrumbList` (D-86) | +| L2 | `` + `usePagination()` | vue-seo-layer | Numbered server-rendered links in the CMS-chosen URL form, plus `` in the head (D-40, D-42) | +| L3 | `SeoHtmlSitemapPage` (page component) | vue-seo-layer | The HTML sitemap (D-34), overridable | +| L4 | `` | vue-seo-layer | Footer link to the HTML sitemap | +| L5 | `JournalPostCard` (changed) | vue-journal-layer | One link per card, configurable heading level (D-80, D-81, D-52) | +| L6 | Heading convention (changed `PostPage` title element, `JournalRelatedPosts`, `JournalCategoryHeader`, `FaqBlock`) | journal, faq | D-52 table, enforced by layer tests in both Journal H1 modes. New journal-layer option `journal.postTitleTag` | +| L7 | Body content rendering (`JournalPostBody` gate) | journal (+ seo utils) | H1 demotion by setting, auto-links, outbound rel, image attributes (D-50, D-71, D-82, D-83) | +| L8 | Label dictionary `useSeoLabels()` + `seo-tokens.css` / `seo-base.css` | vue-seo-layer | en/pl strings without an i18n dependency; app-overridable tokens | +| — | `JournalPagination` (changed) | vue-journal-layer | Renders `` with its `$t` labels. Markup and class names stay the same, so app CSS keeps working | + +--- + +## Spacing Scale + +### Admin (new markup in E8–E10, P6–P9, S1, S2) + +Declared values (all multiples of 4, the same scale as Phase 32): + +| Token | Value | Usage | +|-------|-------|-------| +| xs | 4px | Gap inside a 24px pill between icon and text (E9 node badge); gap between the label and the value in a `.summer-stat` (kit) | +| sm | 8px | Gap between finding lines (E9), suggestion rows (E10) and an E8 select and its type note; icon-to-text gap in status lines | +| md | 16px | Column gap of the E8 type-field grid; gap between E9 nodes; gap between the counters and the lists in E10 | +| lg | 24px | Gap between the sections of E8 (type, fields, custom JSON-LD), E9 (findings, JSON, link) and E10 (counts, two suggestion lists) | +| xl | 32px | Not used by new markup (the column gap of `.summer-stats` is kit-owned) | +| 2xl | 48px | Not used | +| 3xl | 64px | Not used | + +**Exceptions** (all inherited; no new value is introduced): +- Phase 32 Direction C values: label-to-control 6px, form row gap 22px, card padding 28px, page stack 20px, footer button gap 10px, callout padding 14×18px, table cell padding 12×14px, control inner padding 14px. The E8 custom JSON-LD textarea and the E9 JSON `pre` copy E4's 12×14px padding and 10px radius. +- `.summer-stats` kit values: 16×20px padding, 32px column gap. +- J2–J4 copy the Blog Editor's own metrics from `blogeditor.css` (Phase 16.2), so that the new parts match the editor's existing inline forms: bar padding 6×8px, inline-form padding 8px with 6×8px gaps, input padding 5×8px, 2px group gap, 6px input radius. +- Control heights reuse the Direction C set: inputs and selects 44px, buttons 42px, segments and small inline buttons 34px (`--spacing-pager`). E10's "Copy address" button is 34px. +- **J2 outline indent: 12px per heading level** below the first content level (C28). It copies the 12px horizontal step of the Blog Editor's own nested spacing (`blogeditor.css`, Phase 16.2: the 12px inline-form and tool-group gaps), so the outline lines up with the editor chrome around it. It is a multiple of 4, but not a step of the 8-point scale above. +- **`--seo-space-5` = 40px (2.5rem)**, public layer (table below). It copies `--journal-space-5` (`vue-journal-layer/app/assets/css/journal-tokens.css`, "section gap"), so `.seo-pagination` and `.journal-pagination` sit the same distance below a listing. It is a multiple of 4, but not a step of the 8-point scale. + +### Public layers (`vue-seo-layer/app/assets/css/seo-tokens.css`) + +| Token | Value | Usage | +|-------|-------|-------| +| `--seo-space-1` | 0.25rem (4px) | Gap between pagination items; vertical padding of breadcrumb links (24px minimum target height) | +| `--seo-space-2` | 0.5rem (8px) | Horizontal room around the breadcrumb separator; inline padding of pagination links | +| `--seo-space-3` | 1rem (16px) | Gap between previous, the page list and next; gap between HTML sitemap list items in columns | +| `--seo-space-4` | 1.5rem (24px) | Margin under the breadcrumbs; gap between HTML sitemap groups | +| `--seo-space-5` | 2.5rem (40px) | Margin above the pagination (copies `--journal-space-5`, so journal and SEO pagination sit identically) | +| `--seo-target-size` | 2.75rem (44px) | Minimum width and height of pagination links (WCAG 2.5.5 target; copies `--menu-toggle-size`) | +| `--seo-sitemap-column-width` | 16rem | Column width of HTML sitemap lists (`columns: var(--seo-sitemap-column-width)`) | + +These are the only layout values in `seo-base.css`. An app overrides them on `:root:root`, the layer convention. + +--- + +## Typography + +### Admin + +New admin markup uses exactly the three Phase 32 styles. No new size and no new weight. + +The 13px Meta and 14px Body pair is a convention carried over from the approved Phase 32 contract (32-UI-SPEC Typography). That convention is itself the Direction C admin comment size (13px) and body size (14px). Phase 33 keeps it so that the new screens read the same as the Phase 32 SEO screens next to them. The two are told apart by size, by the Label weight (600) and by Meta's muted colour, as in Phase 32. + +| Role | Size | Weight | Line Height | Used by | +|------|------|--------|-------------|---------| +| Body | 14px | 400 | 1.5 | E8 inputs and notes' values, E9 finding text, E10 suggestion titles, P6–P9 and S1–S2 table cells and callout text | +| Label | 14px | 600 | 1.5 | Section headings inside E8–E10, the E8 disclosure button, callout titles, button text | +| Meta | 13px | 400 | 1.5 | E8 type note and JSON status line, E9 header line and source notes, E10 phrase and note lines, the J2 outline items | + +**Inherited, unchanged:** +- 12px/600: row-state and badge pills, reused verbatim for the E9 node-type badges; `.summer-table th`. +- 12px: the Blog Editor's form labels, reused by the J3 and J4 labels and the J2 heading-level tags. +- 20px/600: `.summer-stat__value` in P6 and E10. +- 24px/700 and 26px/700: page headings. + +**Monospace:** DM Mono 400 at 13px with line-height 1.6, the only mono size, for: +- the E8 custom JSON-LD textarea; +- the E9 JSON `pre`; +- URL values in P6, P7 and S1; +- the J2 level tags ("H2"). + +### Public layers + +The layer sets no font family, no heading size and no body size. Everything inherits from the site: + +| Role | Size | Weight | Line Height | Used by | +|------|------|--------|-------------|---------| +| Body | 1em (inherits the site body size; journal default 17px) | 400 | inherit (journal default 1.7) | Pagination links, HTML sitemap lists, the footer sitemap link | +| Small | 0.875em (`--seo-font-size-small`) | 400 | inherit | Breadcrumbs | +| Emphasis | inherit | 600 | inherit | The current page in pagination; the current (last) breadcrumb | +| Headings | inherit the site's `h1`/`h2` styles | inherit | inherit | HTML sitemap H1 and group H2. The layer adds `margin` only, never `font-size` | + +--- + +## Color + +### Admin + +All colours are existing CSS variables (light / dark). New markup must not hard-code a hex value. Phase 33 adds no exception (E-1 stays the only one, scoped to E3). + +| Role | Value (light / dark) | Usage | +|------|----------------------|-------| +| Dominant (60%) | `bg` #f4f6f9 / #111726, `surface` #ffffff / #182033 | Pages, cards, element inputs, the E8 textarea | +| Secondary (30%) | `subtle` #f3f5f8 / #1f283d, `border` #e6e9ef / #29334b, `border-strong` #d2d8e2 / #3a4661, `muted` #566175 / #a9b3c6 | The E9 JSON `pre`, the neutral node badges, the J2 outline bar, notes and source lines, input borders | +| Accent (10%) | `primary` #22304d / #fcd34d; `sel` #fdf3cf / #3a3622; `ring` | See the reserved list | +| Destructive | `danger` #c62828 / #f58a8a, `danger-soft` #fdf0f0 / #3a1d24 | Delete confirmations; 422 lines; the failed-crawl callout (`summer-callout--danger`); E9 "missing required property" lines (with `circle-alert`); the E8 invalid-JSON line; the "Not on the site" row badge (`negative`); "Error" severity text in P7 | +| Semantic OK | `ok-bg` #e3f4e8 / #173826, `ok-text` #1c6b35 / #8fdfa8 | E9 "No problems"; the E8 "Valid JSON" status; E10 counts are neutral, not OK | + +Accent reserved for (exhaustive for this phase): +1. **`primary` fill:** the one existing primary button per screen: footer **Save** on forms and singletons (Pages, Auto-links, SEO settings, sitemap settings, Journal settings) and the list toolbar **create** on Pages ("New page") and Auto-links ("New auto-link"). Toolbar actions ("Start crawl", "Add focus keyphrases") are outline buttons, existing rendering. +2. **`primary` track or fill:** switches when on and checked checkboxes (existing controls), including the J3 rel checkboxes and the J4 "Decorative image" checkbox. +3. **`sel` tint:** `.summer-callout--warning`: + - P6 "limit reached"; + - P9 "Not on the site"; + - the E10 orphan callout; + - the P1 additions "primary category gone"; + - save notices (H1 converted, schema merged). +4. **`ring` (3px):** focus-visible on every focusable element, including every E8–E10 and J2–J4 control (the C14 base rule copied). + +Accent is never used for: severity text, node badges, outline items, links inside partials and elements, or the E10 "Copy address" button (outline). + +Status is never shown by colour alone. Every finding and status line pairs an icon with words, and severities are words (WCAG 1.4.1). + +### Public layers (`seo-tokens.css`) + +The defaults match the journal and menu layer defaults, so an untouched site looks consistent. Every value is an app-overridable token. + +| Role | Token | Default | Usage | +|------|-------|---------|-------| +| Dominant (60%) | none | inherited site background and text | The layer paints no background | +| Secondary (30%) | `--seo-color-muted` | #59636e (6.2:1 on white) | Breadcrumb separators, the current breadcrumb, the pagination gap `…`, HTML sitemap counts | +| Accent (10%) | `--seo-color-accent` | #0969da | The current page in `.seo-pagination` (background) and the focus ring of every layer link | +| On accent | `--seo-color-on-accent` | #ffffff (5.0:1 on #0969da) | Text of the current pagination page | +| Border | `--seo-color-border` | #d1d9e0 | Not used by default; reserved for themes (documented) | +| Destructive | none | — | The layer has no destructive action | + +Accent reserved for: (1) the current-page marker in `.seo-pagination`; (2) the 2px `:focus-visible` outline on layer links. Links inherit the site's link colour; the layer sets none. + +--- + +## Copywriting Contract + +**Conventions** (unchanged from Phase 32): +- Server copy lives in each plugin's `lang/{en,pl}/lang.yaml`: + - `golem15.seo::lang.*` (keys below without a prefix are SEO keys); + - `golem15.sitemap::lang.*` (prefixed `sitemap:`); + - `golem15.journal::lang.*` (prefixed `journal:`). +- Element copy lives in each element's bundled `labels.ts`, chosen by `ui-locale`. +- Placeholders: `:name` in admin copy; `{name}` in the Blog Editor `labels.ts` (its existing `format`) and in layer copy (the journal layer i18n convention). +- Plurals are CLDR maps: `one`/`other` in en; `one`/`few`/`many`/`other` in pl. Never pipe strings. +- Quotes: “…” in en and „…” in pl. +- Existing framework copy (`form.*`, `list.*`, validation phrases, `extension.*`) is reused and not repeated here. +- Layer copy lives in the L8 dictionary, except journal strings, which stay in `vue-journal-layer/i18n/locales/*.json`. + +### Primary CTAs and key states + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Pages primary CTA (toolbar create; create-form title) | `pages.new` | New page | Nowa strona | +| Auto-links primary CTA | `autolinks.new` | New auto-link | Nowy automatyczny link | +| Start a crawl (toolbar action on Site audit and Crawls) | `crawls.start` | Start crawl | Uruchom skanowanie | +| Add focus keyphrases (Auto-links toolbar action) | `autolinks.from_keyphrases` | Add focus keyphrases | Dodaj frazy kluczowe | +| E9 re-check (widget action label) | `graph.action` | Check structured data | Sprawdź dane strukturalne | +| Issue record action | `issues.recheck` | Check this address again | Sprawdź ten adres ponownie | +| Pages empty state | `pages.list_empty` | No pages yet. Add the site's own pages here, such as the home page, contact or offer pages, so they get an SEO tab and a place in the sitemaps. The site can also report its pages automatically. | Nie ma jeszcze stron. Dodaj tu własne strony witryny, np. stronę główną, kontakt lub strony ofertowe, aby miały zakładkę SEO i miejsce w mapach witryny. Witryna może też zgłaszać swoje strony automatycznie. | +| Auto-links empty state | `autolinks.list_empty` | No auto-links yet. Add a phrase and the page it should link to; texts then link its first occurrence automatically. | Nie ma jeszcze automatycznych linków. Dodaj frazę i stronę, do której ma prowadzić; teksty będą wtedy automatycznie linkować jej pierwsze wystąpienie. | +| Site audit empty state | `issues.list_empty` | No issues. Problems found by the last crawl and by the CMS data checks appear here. Start a crawl to check the site again. | Brak problemów. Tutaj pojawiają się problemy z ostatniego skanowania i z kontroli danych CMS. Uruchom skanowanie, aby ponownie sprawdzić witrynę. | +| Crawls empty state | `crawls.list_empty` | No crawls yet. A crawl fetches the site like a search engine and lists what it finds in SEO › Site audit. | Nie było jeszcze skanowań. Skanowanie pobiera witrynę jak wyszukiwarka i wypisuje znalezione problemy w SEO › Audyt witryny. | +| Filtered empty, load errors, element and action errors | existing `messages.list.empty_search`, `list.load_failed`, `form.load_failed`, `extension.widget_failed`, `extension.action_failed` | unchanged | unchanged | + +### Navigation, settings tile, permission (changes and additions) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Side menu: Pages (icon `files`) | `nav.pages` | Pages | Strony | +| Side menu: Auto-links (icon `wand-sparkles`) | `nav.autolinks` | Auto-links | Automatyczne linki | +| Side menu: Site audit (icon `gauge`) | `nav.audit` | Site audit | Audyt witryny | +| Side menu: Crawls (icon `globe`) | `nav.crawls` | Crawls | Skanowania | +| Settings tile description (replaces the Phase 32 text) | `settings.description` | Title templates, default descriptions, indexing, addresses, pagination, links, publisher data, site audit and search engine verification. | Szablony tytułów, domyślne opisy, indeksowanie, adresy, stronicowanie, linki, dane wydawcy, audyt witryny i weryfikacja w wyszukiwarkach. | +| Permission comment (replaces the Phase 32 text) | `permissions.manage_comment` | Edit the SEO tab of every content type, URL rules, redirects, pages, auto-links, robots.txt and the SEO settings, and run the site audit, in the admin and through MCP. | Edycja zakładki SEO we wszystkich typach treści, reguł adresów, przekierowań, stron, automatycznych linków, robots.txt i ustawień SEO oraz uruchamianie audytu witryny, w panelu i przez MCP. | + +### SEO tab additions (C16) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Primary category label | `field.primary_category` | Primary category (breadcrumb trail) | Kategoria główna (ścieżka nawigacji) | +| Primary category comment | `field.primary_category_comment` | Sets the breadcrumb trail and the article section. Default: the first category. | Ustala ścieżkę nawigacji i sekcję artykułu. Domyślnie: pierwsza kategoria. | +| Primary category empty option | `field.primary_category_default` | Default (first category) | Domyślna (pierwsza kategoria) | +| Structured data label (E8) | `field.schema` | Structured data | Dane strukturalne | +| Structured data comment | `field.schema_comment` | The schema.org type of this page in its JSON-LD graph. Shared by all languages. | Typ schema.org tej strony w jej grafie JSON-LD. Wspólny dla wszystkich wersji językowych. | +| Graph preview label (E9) | `field.graph` | Structured data preview | Podgląd danych strukturalnych | +| Internal links label (E10) | `field.links` | Internal links | Linki wewnętrzne | +| P1 callout: primary category gone (warning) | `status.primary_gone` | The primary category “:name” is no longer assigned, so the first category is used. Choose another one below. | Kategoria główna „:name” nie jest już przypisana, więc używana jest pierwsza kategoria. Wybierz inną poniżej. | +| P1 callout title: left out of sitemaps (plain) | `status.excluded_title` | Not in the sitemaps | Poza mapami witryny | +| P1 text: noindex | `status.excluded_noindex` | This page is set to noindex, so the sitemaps leave it out. | Ta strona ma ustawione noindex, więc mapy witryny ją pomijają. | +| P1 text: canonical elsewhere | `status.excluded_canonical` | The canonical URL points to “:url”, so the sitemaps list that address instead. | Adres kanoniczny wskazuje „:url”, więc mapy witryny podają tamten adres. | +| P1 text: redirected | `status.excluded_redirect` | This address redirects to “:url”, so the sitemaps leave it out. | Ten adres przekierowuje na „:url”, więc mapy witryny go pomijają. | +| P1 text: type off | `status.excluded_type` | Its content type is turned off in the sitemap settings. | Jej typ treści jest wyłączony w ustawieniach map witryny. | + +### E8 `golem15-seo-schema` (element copy) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Type select label | `type` | Schema type | Typ schema | +| Default option | `typeDefault` | Default (:type) | Domyślny (:type) | +| BlogPosting | `tBlogPosting` | BlogPosting (blog post) | BlogPosting (wpis na blogu) | +| Article | `tArticle` | Article (article) | Article (artykuł) | +| NewsArticle | `tNewsArticle` | NewsArticle (news) | NewsArticle (wiadomość) | +| WebPage | `tWebPage` | WebPage (page) | WebPage (strona) | +| AboutPage | `tAboutPage` | AboutPage (about us) | AboutPage (o nas) | +| ContactPage | `tContactPage` | ContactPage (contact) | ContactPage (kontakt) | +| Service | `tService` | Service (service or offer) | Service (usługa lub oferta) | +| CollectionPage | `tCollectionPage` | CollectionPage (listing) | CollectionPage (lista) | +| ProfilePage | `tProfilePage` | ProfilePage (profile) | ProfilePage (profil) | +| Note: article types | `noteArticle` | Author, dates, image, word count and section come from the content. | Autor, daty, obraz, liczba słów i sekcja pochodzą z treści. | +| Note: WebPage, AboutPage | `notePage` | Name, description and breadcrumbs come from the SEO tab. | Nazwa, opis i ścieżka nawigacji pochodzą z zakładki SEO. | +| Note: ContactPage | `noteContact` | Address and opening hours come from the SEO settings (Publisher, Local business). | Adres i godziny otwarcia pochodzą z ustawień SEO (Wydawca, Firma lokalna). | +| Note: Service | `noteService` | The provider is the publisher from the SEO settings. | Usługodawcą jest wydawca z ustawień SEO. | +| Note: CollectionPage | `noteCollection` | Lists the items shown on the page. | Wymienia elementy pokazane na stronie. | +| Note: ProfilePage | `noteProfile` | Name, image and profiles come from the author. | Imię i nazwisko, zdjęcie i profile pochodzą od autora. | +| Service: kind / placeholder | `serviceType` / `serviceTypePh` | Kind of service / e.g. Mushroom workshops | Rodzaj usługi / np. warsztaty grzybowe | +| Service: area / hint | `areaServed` / `areaServedHint` | Area served / Cities or regions, separated by commas. | Obszar działania / Miasta lub regiony oddzielone przecinkami. | +| Service: price / hint | `price` / `priceHint` | Price / Leave empty when the price is set individually. | Cena / Zostaw puste, gdy cena jest ustalana indywidualnie. | +| Service: currency / hint | `currency` / `currencyHint` | Currency / ISO code, e.g. PLN. | Waluta / Kod ISO, np. PLN. | +| Invalid price | `priceInvalid` | Enter a price such as 150 or 149.99. | Wpisz cenę, np. 150 lub 149,99. | +| Invalid currency | `currencyInvalid` | Use a three-letter currency code, e.g. PLN. | Użyj trzyliterowego kodu waluty, np. PLN. | +| Custom disclosure | `custom` | Custom JSON-LD (advanced) | Własny JSON-LD (zaawansowane) | +| Custom summary when set | `customSet` | Set | Ustawiony | +| Custom hint | `customHint` | An object or a list of objects. A node with the @id of a generated node is merged into it, and your values win; other nodes are added to the graph. Paste only the JSON, without a script tag. | Obiekt lub lista obiektów. Węzeł z @id wygenerowanego węzła zostaje z nim scalony, a Twoje wartości mają pierwszeństwo; pozostałe węzły są dodawane do grafu. Wklej sam JSON, bez znacznika script. | +| Valid JSON | `jsonValid` | one: "Valid JSON: :count node" other: "Valid JSON: :count nodes" | one: "Poprawny JSON: :count węzeł" few: "Poprawny JSON: :count węzły" many: "Poprawny JSON: :count węzłów" other: "Poprawny JSON: :count węzła" | +| Parse error | `jsonError` | Line :line, column :column: :message | Wiersz :line, kolumna :column: :message | +| Not an object | `jsonNotObject` | Use an object or a list of objects. | Użyj obiektu lub listy obiektów. | +| Too large | `jsonTooLarge` | Over 16 KB: shorten the custom JSON-LD. | Ponad 16 KB: skróć własny JSON-LD. | + +**Server copy for E8** (422 from `Decode`; a save notice through `FormFieldDecoded.Notices`): + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Invalid JSON (422) | `schema.invalid_json` | The custom JSON-LD is not valid JSON: :error | Własny JSON-LD nie jest poprawnym JSON-em: :error | +| Not an object (422) | `schema.not_object` | The custom JSON-LD must be an object or a list of objects. | Własny JSON-LD musi być obiektem lub listą obiektów. | +| Too large (422) | `schema.too_large` | The custom JSON-LD may be at most 16 KB. | Własny JSON-LD może mieć najwyżej 16 KB. | +| Too deep (422) | `schema.too_deep` | The custom JSON-LD is nested more than 8 levels deep. | Własny JSON-LD jest zagnieżdżony głębiej niż 8 poziomów. | +| Script tag (422) | `schema.script_tag` | Remove the script tag and paste only the JSON. | Usuń znacznik script i wklej sam JSON. | +| Type not allowed (422) | `schema.type_unknown` | This schema type is not available for :type. | Ten typ schema jest niedostępny dla: :type. | +| Field invalid (422) | `schema.field_invalid` | :field: :message | :field: :message | +| Merge notice (save notice) | `schema.merged` | Saved. Your custom JSON-LD replaces :props in the generated :node node. | Zapisano. Twój JSON-LD zastępuje :props w wygenerowanym węźle :node. | + +### E9 `golem15-seo-graph` (element copy) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Header | `forLocale` | JSON-LD for :locale — :url | JSON-LD dla :locale — :url | +| Building | `checking` | Building the graph… | Budowanie grafu… | +| No problems | `allGood` | No problems: every node has its required and recommended properties. | Brak problemów: każdy węzeł ma wymagane i zalecane właściwości. | +| Missing required | `required` | Missing required property “:property” (:type) | Brak wymaganej właściwości „:property” (:type) | +| Missing recommended | `recommended` | Missing recommended property “:property” (:type) | Brak zalecanej właściwości „:property” (:type) | +| Type without Google rules | `noRules` | :type has no rich-result rules at Google; it is checked as valid schema.org only. | :type nie ma reguł wyników z elementami rozszerzonymi w Google; sprawdzana jest tylko poprawność schema.org. | +| Show / hide JSON | `showJson` / `hideJson` | Show JSON-LD / Hide JSON-LD | Pokaż JSON-LD / Ukryj JSON-LD | +| Rich Results link | `richResults` | Check in Google's Rich Results Test | Sprawdź w Teście wyników z elementami rozszerzonymi Google | +| No address yet | `noUrl` | The Rich Results Test link appears after the first save, when the page has an address. | Link do testu pojawi się po pierwszym zapisie, gdy strona będzie miała adres. | +| Unsaved note | `unsaved` | Shows the saved page with your unsaved structured data changes. | Pokazuje zapisaną stronę z niezapisanymi zmianami danych strukturalnych. | +| Layer nodes note | `layerNodes` | The site may add nodes of its own, such as FAQPage. The site audit checks the rendered result. | Witryna może dodać własne węzły, np. FAQPage. Audyt witryny sprawdza wynik na stronie. | + +### E10 `golem15-seo-links` (element copy) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Inbound counter label | `inbound` | Links to this page | Linki do tej strony | +| Outbound counter label | `outbound` | Links from this page | Linki z tej strony | +| Crawl source | `crawled` | From the crawl of :time. | Według skanowania z :time. | +| Not crawled | `notCrawled` | Link counts appear after the first crawl (SEO › Site audit). | Liczby linków pojawią się po pierwszym skanowaniu (SEO › Audyt witryny). | +| Orphan callout title / text (warning) | `orphanTitle` / `orphanText` | No page links here / Search engines and visitors may not find this page. Link to it from one of the pages below. | Żadna strona tu nie linkuje / Wyszukiwarki i odwiedzający mogą nie znaleźć tej strony. Dodaj do niej link z jednej ze stron poniżej. | +| Suggestions out: heading | `linkOut` | Pages you could link to from this text | Strony, do których możesz dodać link w tym tekście | +| Suggestions in: heading | `linkIn` | Pages that could link here | Strony, które mogą tu linkować | +| Suggestion line | `phrase` | “:phrase” → :title | „:phrase” → :title | +| Copy button / done | `copy` / `copied` | Copy address / Address copied. | Kopiuj adres / Skopiowano adres. | +| No suggestions | `none` | No suggestions. They come from titles and focus keyphrases found in the saved text. | Brak propozycji. Powstają z tytułów i fraz kluczowych znalezionych w zapisanym tekście. | +| Before the first save | `noRecord` | Suggestions appear after the first save. | Propozycje pojawią się po pierwszym zapisie. | + +### SEO settings: new tabs and fields (C20) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| New tabs | `settings.tab_pagination` / `_links` / `_publisher` / `_local` / `_audit` | Pagination / Links / Publisher / Local business / Site audit | Stronicowanie / Linki / Wydawca / Firma lokalna / Audyt witryny | +| General: home crumb | `settings.breadcrumb_home` | Home page name in breadcrumbs | Nazwa strony głównej w ścieżce nawigacji | +| Home crumb comment | `settings.breadcrumb_home_comment` | The first item of every breadcrumb trail, also in the BreadcrumbList structured data. | Pierwszy element każdej ścieżki nawigacji, także w danych strukturalnych BreadcrumbList. | +| Home crumb default | `settings.breadcrumb_home_default` | Home | Strona główna | +| Page address form | `settings.page_form` | Page address form | Forma adresu strony | +| Form options | `settings.page_form_query` / `_path` | Query parameter: /blog?page=2 / Path segment: /blog/strona/2 | Parametr: /blog?page=2 / Segment ścieżki: /blog/strona/2 | +| Form comment | `settings.page_form_comment` | A change sends every address in the other form to this one with a 301. ?page=1 always redirects to the address without a number. | Zmiana przekierowuje (301) każdy adres w drugiej formie na tę. ?page=1 zawsze przekierowuje na adres bez numeru. | +| Path segment | `settings.page_segment` | Path segment | Segment ścieżki | +| Segment comment | `settings.page_segment_comment` | Used with the path form, per language, e.g. strona or page. | Używany w formie ścieżki, osobno dla każdego języka, np. strona lub page. | +| Title suffix | `settings.page_title_suffix` | Title suffix on page 2 and later | Dopisek do tytułu od strony 2 | +| Title suffix comment | `settings.page_title_suffix_comment` | Added after the page title, before the rest of the title template. Placeholders: %page%, %pages%. | Dodawany po tytule strony, przed resztą szablonu tytułu. Znaczniki: %page%, %pages%. | +| Title suffix default | `settings.page_title_suffix_default` | – page %page% of %pages% | – strona %page% z %pages% | +| Description suffix | `settings.page_description_suffix` | Description suffix on page 2 and later | Dopisek do opisu od strony 2 | +| Description suffix comment | `settings.page_description_suffix_comment` | Added at the end of the meta description. Placeholders: %page%, %pages%. | Dodawany na końcu opisu meta. Znaczniki: %page%, %pages%. | +| Description suffix default | `settings.page_description_suffix_default` | Page %page% of %pages%. | Strona %page% z %pages%. | +| Invalid segment (422) | `settings.page_segment_invalid` | Use lowercase letters, digits and hyphens only, e.g. strona. | Użyj tylko małych liter, cyfr i łączników, np. strona. | +| Auto-links per page | `settings.autolink_max` | Auto-links per page | Automatyczne linki na stronę | +| Auto-links comment | `settings.autolink_max_comment` | At most this many phrases from SEO › Auto-links are linked in one text. 0 turns auto-linking off. | Najwyżej tyle fraz z SEO › Automatyczne linki zostaje podlinkowanych w jednym tekście. 0 wyłącza automatyczne linkowanie. | +| nofollow domains | `settings.nofollow_domains` | Domains that always get nofollow | Domeny zawsze z nofollow | +| sponsored domains | `settings.sponsored_domains` | Domains that always get sponsored | Domeny zawsze ze sponsored | +| Domains comment | `settings.domains_comment` | One domain per line, e.g. example.com (subdomains included). | Jedna domena w wierszu, np. example.com (razem z subdomenami). | +| New-tab switch | `settings.links_new_tab` | Open links to other sites in a new tab | Otwieraj linki do innych witryn w nowej karcie | +| New-tab switch comment | `settings.links_new_tab_comment` | On: links to other sites open in a new tab with rel=noopener, and screen readers hear that they do. Off: they open in the same tab. Their nofollow, sponsored and ugc attributes apply either way. | Włączone: linki do innych witryn otwierają się w nowej karcie z rel=noopener, a czytniki ekranu o tym informują. Wyłączone: otwierają się w tej samej karcie. Atrybuty nofollow, sponsored i ugc działają w obu przypadkach. | +| Invalid domain (422) | `settings.domain_invalid` | Line :line: “:value” is not a domain. Write it without https:// and without a path. | Wiersz :line: „:value” nie jest domeną. Wpisz ją bez https:// i bez ścieżki. | +| Publisher type | `settings.publisher_type` | Publisher type | Typ wydawcy | +| Publisher options | `settings.publisher_org` / `_person` | Organisation / Person (personal site) | Organizacja / Osoba (strona prywatna) | +| Publisher type comment | `settings.publisher_type_comment` | Who publishes the site, in the structured data of every page. | Kto wydaje witrynę, w danych strukturalnych każdej strony. | +| Name / comment | `settings.publisher_name` / `_comment` | Name / Empty: the site name. | Nazwa / Puste: nazwa witryny. | +| Logo / comment | `settings.publisher_logo` / `_comment` | Logo or photo / Organisation: the logo, at least 112 × 112 px. Person: a photo. | Logo lub zdjęcie / Organizacja: logo, co najmniej 112 × 112 px. Osoba: zdjęcie. | +| Website / comment | `settings.publisher_url` / `_comment` | Website / Empty: the site address. | Strona internetowa / Puste: adres witryny. | +| sameAs / comment | `settings.publisher_same_as` / `_comment` | Profiles elsewhere (sameAs) / One address per line: social profiles, Wikipedia, the company register entry. | Profile w innych serwisach (sameAs) / Jeden adres w wierszu: profile społecznościowe, Wikipedia, wpis w KRS lub CEIDG. | +| Invalid sameAs (422) | `settings.same_as_invalid` | Line :line: use a full address starting with https://. | Wiersz :line: wpisz pełny adres zaczynający się od https://. | +| Email / phone | `settings.publisher_email` / `_phone` | Contact email / Phone | E-mail kontaktowy / Telefon | +| Phone comment | `settings.publisher_phone_comment` | With the country code, e.g. +48 600 000 000. | Z numerem kierunkowym kraju, np. +48 600 000 000. | +| P8 label | `settings.publisher_preview` | Generated publisher data | Wygenerowane dane wydawcy | +| Local switch / comment | `settings.local_enabled` / `_comment` | Publish as a local business / For a business with an address that customers visit, or with a service area. Not available for a person. | Publikuj jako firmę lokalną / Dla firmy z adresem, który odwiedzają klienci, lub z obszarem obsługi. Niedostępne dla osoby. | +| Kind of business | `settings.local_type` | Kind of business | Rodzaj działalności | +| Kind options | `settings.local_type_*` (LocalBusiness, ProfessionalService, Store, FoodEstablishment, HealthAndBeautyBusiness, MedicalBusiness, LegalService, HomeAndConstructionBusiness, AutomotiveBusiness, FinancialService) | Local business (general) / Professional services / Shop / Restaurant or café / Health and beauty / Medical practice / Legal services / Home and construction / Automotive / Financial services | Firma lokalna (ogólnie) / Usługi profesjonalne / Sklep / Restauracja lub kawiarnia / Zdrowie i uroda / Praktyka medyczna / Usługi prawne / Dom i budownictwo / Motoryzacja / Usługi finansowe | +| Price range / comment | `settings.local_price_range` / `_comment` | Price range / e.g. 50–200 zł or $$. | Przedział cen / np. 50–200 zł lub $$. | +| Address fields | `settings.local_street` / `_postal_code` / `_city` / `_region` / `_country` | Street and number / Postal code / City / Region / Country code | Ulica i numer / Kod pocztowy / Miejscowość / Województwo lub region / Kod kraju | +| Country comment | `settings.local_country_comment` | Two letters, e.g. PL. | Dwie litery, np. PL. | +| Coordinates / comment | `settings.local_latitude` / `_longitude` / `settings.local_coordinates_comment` | Latitude / Longitude / Decimal degrees, e.g. 52.2297. | Szerokość geograficzna / Długość geograficzna / Stopnie dziesiętne, np. 52.2297. | +| Opening hours repeater / add | `settings.local_hours` / `_add` | Opening hours / Add opening hours | Godziny otwarcia / Dodaj godziny otwarcia | +| Opening hours comment | `settings.local_hours_comment` | Add two rows for one day to split it, e.g. 9:00–13:00 and 14:00–18:00. A day without rows is closed. | Dodaj dwa wiersze dla jednego dnia, aby go podzielić, np. 9:00–13:00 i 14:00–18:00. Dzień bez wierszy jest nieczynny. | +| Row: day / opens / closes | `settings.hours_day` / `_opens` / `_closes` | Day / Opens / Closes | Dzień / Otwarcie / Zamknięcie | +| Day options | `settings.day_mon` … `_sun` | Monday … Sunday | Poniedziałek … Niedziela | +| Time comment | `settings.hours_time_comment` | 24-hour time, e.g. 09:00. | Format 24-godzinny, np. 09:00. | +| Person + local (422) | `settings.local_person` | A person cannot be a local business. Change the publisher type to Organisation, or turn this off. | Osoba nie może być firmą lokalną. Zmień typ wydawcy na Organizacja lub wyłącz tę opcję. | +| Address missing (422) | `settings.local_address_required` | A local business needs a street, a postal code and a city. | Firma lokalna wymaga ulicy, kodu pocztowego i miejscowości. | +| Time invalid (422) | `settings.time_invalid` | Use 24-hour time, e.g. 09:00. | Użyj formatu 24-godzinnego, np. 09:00. | +| Hours order (422) | `settings.hours_order` | Closing must come after opening. | Godzina zamknięcia musi być późniejsza niż otwarcia. | +| Country invalid (422) | `settings.country_invalid` | Use a two-letter country code, e.g. PL. | Użyj dwuliterowego kodu kraju, np. PL. | +| Coordinates invalid (422) | `settings.coordinate_invalid` | Enter decimal degrees: latitude from -90 to 90, longitude from -180 to 180. | Wpisz stopnie dziesiętne: szerokość od -90 do 90, długość od -180 do 180. | +| Crawl schedule / options | `settings.crawl_schedule` / `_off` / `_daily` / `_weekly` | Automatic crawl / Off / Daily / Weekly | Automatyczne skanowanie / Wyłączone / Codziennie / Co tydzień | +| Schedule comment | `settings.crawl_schedule_comment` | Runs at night. You can always start a crawl by hand in SEO › Crawls. | Uruchamiane w nocy. Skanowanie zawsze możesz uruchomić ręcznie w SEO › Skanowania. | +| Max pages / comment | `settings.crawl_max_pages` / `_comment` | Pages per crawl / The crawl stops after this many pages. Default 5000. | Stron na skanowanie / Skanowanie kończy się po tylu stronach. Domyślnie 5000. | +| Delay / comment | `settings.crawl_delay` / `_comment` | Pause between requests (ms) / At most two requests run at a time. A longer pause spares a small server. | Przerwa między żądaniami (ms) / Jednocześnie trwają najwyżej dwa żądania. Dłuższa przerwa oszczędza mały serwer. | +| Keep / comment | `settings.crawl_keep` / `_comment` | Crawls kept / Older crawls are deleted with their pages and issues. | Przechowywane skanowania / Starsze skanowania są usuwane razem ze stronami i problemami. | +| Generic anchors / comment | `settings.generic_anchors` / `_comment` | Generic link texts / One per line. The crawl reports links whose whole text is one of these, ignoring letter case. | Ogólnikowe teksty linków / Jeden w wierszu. Skanowanie zgłasza linki, których cały tekst jest jednym z nich, bez względu na wielkość liter. | + +The number ranges use the existing framework `validation.between` phrase. They are 0–20 for auto-links per page, 10–50 000 for pages per crawl, 100–5000 for the delay and 1–50 for crawls kept. + +### Pages (page registry, C21) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| List title / record name | `pages.list_title` / `pages.name` | Pages / Page | Strony / Strona | +| Columns | `pages.url` / `_title` / `_kind` / `_origin` / `_content_updated` / `_reported` | Address / Name / Type / Origin / Content changed / Last reported | Adres / Nazwa / Typ / Pochodzenie / Zmiana treści / Ostatnie zgłoszenie | +| Kind options | `pages.kind_page` / `_offer` / `_service` / `_landing` | Page / Offer / Service / Landing page | Strona / Oferta / Usługa / Landing page | +| Origin values | `pages.origin_manual` / `_reported` / `_mcp` | Manual / Reported by the site / MCP | Ręcznie / Zgłoszona przez witrynę / MCP | +| Row state `negative` | `pages.state_missing` | Not on the site | Brak w witrynie | +| Filter: missing (scope) | `pages.filter_missing` / `_only` / `_hide` | Not on the site / Only missing / Hide missing | Brak w witrynie / Tylko brakujące / Ukryj brakujące | +| Address comment | `pages.url_comment` | The page's path in each language, e.g. /kontakt. Leave a language empty when the page has no version in it. | Ścieżka strony w każdym języku, np. /kontakt. Zostaw język pusty, jeśli strona nie ma w nim wersji. | +| Name comment | `pages.title_comment` | Used for the default SEO title, the HTML sitemap and breadcrumbs. | Używana w domyślnym tytule SEO, mapie HTML i ścieżce nawigacji. | +| Type comment | `pages.kind_comment` | Decides the sitemap file and the default structured data: offers and services become Service. | Decyduje o pliku mapy witryny i domyślnych danych strukturalnych: oferty i usługi stają się typem Service. | +| Parent / empty option / comment | `pages.parent` / `_none` / `_comment` | Parent page / None (top level) / Places the page under another one in breadcrumbs. | Strona nadrzędna / Brak (najwyższy poziom) / Umieszcza stronę pod inną w ścieżce nawigacji. | +| Content changed comment | `pages.content_updated_comment` | Sent as lastmod in the sitemaps. Change it when the page's content changes on the site. | Wysyłana jako lastmod w mapach witryny. Zmień ją, gdy treść strony zmieni się w witrynie. | +| Note / comment | `pages.note` / `_comment` | Note / Not shown on the site. | Notatka / Nie jest wyświetlana na stronie. | +| P9 reported (plain) | `pages.status_reported` | Reported by the site on :time. | Zgłoszona przez witrynę :time. | +| P9 missing (warning) | `pages.status_missing` | The site's last report (:time) did not include this address. Check whether the page still exists, then delete it here or redirect it. | Ostatnie zgłoszenie witryny (:time) nie zawierało tego adresu. Sprawdź, czy strona nadal istnieje, a potem usuń ją tutaj lub ustaw przekierowanie. | +| Default-language address missing (422) | `pages.url_required` | Enter the address in the default language. | Wpisz adres w języku domyślnym. | +| Invalid address (422) | `pages.url_invalid` | Start the address with / and leave out the domain. | Zacznij adres od / i pomiń domenę. | +| Duplicate (422) | `pages.url_taken` | Another page already uses this address. | Inna strona już używa tego adresu. | +| Owned by a record (422) | `pages.url_owned` | This address belongs to :type “:title”. Set its SEO there. | Ten adres należy do: :type „:title”. Ustaw jego SEO tam. | +| Parent loop (422) | `pages.parent_loop` | A page cannot be placed under itself or one of its subpages. | Strony nie można umieścić pod nią samą ani pod jej podstroną. | +| Delete confirm | `pages.delete_confirm` | Delete this page from the registry? Its SEO tab is deleted with it, and the address leaves the sitemaps. A page the site reports comes back with its next report. | Usunąć tę stronę z rejestru? Jej zakładka SEO zostanie usunięta, a adres zniknie z map witryny. Strona zgłaszana przez witrynę wróci przy następnym zgłoszeniu. | +| Delete selected confirm | `pages.delete_selected_confirm` | one: "Delete :count page from the registry? Its SEO tab is deleted with it." other: "Delete :count pages from the registry? Their SEO tabs are deleted with them." | one: "Usunąć :count stronę z rejestru? Jej zakładka SEO zostanie usunięta." few: "Usunąć :count strony z rejestru? Ich zakładki SEO zostaną usunięte." many: "Usunąć :count stron z rejestru? Ich zakładki SEO zostaną usunięte." other: "Usunąć :count strony z rejestru? Ich zakładki SEO zostaną usunięte." | + +### Auto-links (C22) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| List title / record name | `autolinks.list_title` / `autolinks.name` | Auto-links / Auto-link | Automatyczne linki / Automatyczny link | +| Columns | `autolinks.phrase` / `_url` / `_locale` / `_origin` / `_updated` | Phrase / Links to / Language / Origin / Updated | Fraza / Prowadzi do / Język / Pochodzenie / Zaktualizowano | +| Locale value "all" and option | `autolinks.locale_all` | All languages | Wszystkie języki | +| Origin values | `autolinks.origin_manual` / `_keyphrase` / `_mcp` | Manual / Focus keyphrase / MCP | Ręcznie / Fraza kluczowa / MCP | +| Enabled switch | `autolinks.enabled` | Active | Aktywny | +| Row state `disabled` | `autolinks.state_off` | Off | Wyłączony | +| Phrase comment | `autolinks.phrase_comment` | Linked at its first occurrence in a text, as whole words, ignoring letter case. Never inside headings or existing links, and never on the page it links to. | Linkowana przy pierwszym wystąpieniu w tekście, jako całe słowa, bez względu na wielkość liter. Nigdy w nagłówkach ani istniejących linkach i nigdy na stronie, do której prowadzi. | +| URL comment | `autolinks.url_comment` | A path such as /grzyby/lwia-grzywa, or a full address. | Ścieżka, np. /grzyby/lwia-grzywa, lub pełny adres. | +| Note | `autolinks.note` | Note | Notatka | +| Add keyphrases result (toast) | `autolinks.keyphrases_added` | one: ":count phrase added from focus keyphrases." other: ":count phrases added from focus keyphrases." | one: "Dodano :count frazę z fraz kluczowych." few: "Dodano :count frazy z fraz kluczowych." many: "Dodano :count fraz z fraz kluczowych." other: "Dodano :count frazy z fraz kluczowych." | +| Nothing to add (toast) | `autolinks.keyphrases_none` | No new focus keyphrases to add. | Brak nowych fraz kluczowych do dodania. | +| Phrase too short (422) | `autolinks.phrase_short` | Use at least 3 characters. | Użyj co najmniej 3 znaków. | +| Duplicate phrase (422) | `autolinks.phrase_taken` | This phrase already links to “:url” in this language. | Ta fraza już prowadzi do „:url” w tym języku. | +| Invalid URL (422) | `autolinks.url_invalid` | Use a path starting with / or a full http(s) address. | Użyj ścieżki zaczynającej się od / lub pełnego adresu http(s). | +| Delete confirm | `autolinks.delete_confirm` | Delete this auto-link? Texts stop linking the phrase when their pages are next rendered. | Usunąć ten automatyczny link? Teksty przestaną linkować frazę przy następnym wygenerowaniu stron. | +| Delete selected confirm | `autolinks.delete_selected_confirm` | one: "Delete :count auto-link?" other: "Delete :count auto-links?" | one: "Usunąć :count automatyczny link?" few: "Usunąć :count automatyczne linki?" many: "Usunąć :count automatycznych linków?" other: "Usunąć :count automatycznego linku?" | + +### Site audit (issues, C23) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| List title / record name | `issues.list_title` / `issues.name` | Site audit / Issue | Audyt witryny / Problem | +| Columns | `issues.severity` / `_kind` / `_url` / `_summary` / `_source` / `_fix` / `_found` / `_locale` | Severity / Problem / Address / Details / Found by / Fix / Found / Language | Waga / Problem / Adres / Szczegóły / Wykryte przez / Naprawa / Wykryto / Język | +| Severity values | `issues.sev_error` / `_warning` / `_notice` | Error / Warning / Notice | Błąd / Ostrzeżenie / Uwaga | +| Source values | `issues.source_crawl` / `_cms` | Crawl / CMS data check | Skanowanie / Kontrola danych CMS | +| Fix values | `issues.fix_tool` / `issues.fix_code` | MCP: :tool / Site templates (code) | MCP: :tool / Szablony witryny (kod) | +| Row state `disabled` | `issues.state_ignored` | Ignored | Zignorowany | +| Filters | `issues.filter_severity` / `_kind` / `_source` / `_fix` / `_status` | Severity / Problem / Found by / Fix / Status | Waga / Problem / Wykryte przez / Naprawa / Stan | +| Fix filter options | `issues.filter_fix_tool` / `_code` | Fixable through MCP / Needs a code change | Do naprawy przez MCP / Wymaga zmiany w kodzie | +| Status filter options | `issues.status_open` / `_ignored` | Open / Ignored | Otwarte / Zignorowane | +| Preview subtitle | `issues.preview` | Issue details | Szczegóły problemu | +| P7 sections | `issues.found_heading` / `issues.fix_heading` | What was found / How to fix | Co znaleziono / Jak naprawić | +| P7 fix: tool | `issues.fix_tool_text` | Ask your MCP client to fix it with the tool “:tool”, or change it in the admin: :where. | Poproś klienta MCP o naprawę narzędziem „:tool” lub zmień to w panelu: :where. | +| P7 fix: code | `issues.fix_code_text` | This comes from the site's templates, not from CMS data. Ask the site's developer to change :what. | To wynika z szablonów witryny, a nie z danych CMS. Poproś dewelopera witryny o zmianę: :what. | +| P7 open page | `issues.open_page` | Open the page | Otwórz stronę | +| P7 Rich Results link | `issues.rich_results` | Check in Google's Rich Results Test | Sprawdź w Teście wyników z elementami rozszerzonymi Google | +| P7 linked from | `issues.linked_from` | Linked from | Linkowane z | +| P7 orphan suggestions heading / columns | `issues.orphan_suggestions` / `_page` / `_phrase` | Pages that could link here / Page / Shared phrase | Strony, które mogą tu linkować / Strona / Wspólna fraza | +| P7 orphan, none | `issues.orphan_none` | No suggestions: no other page mentions this page's title or focus keyphrase. | Brak propozycji: żadna inna strona nie wspomina tytułu ani frazy kluczowej tej strony. | +| More in list | `issues.and_more` | and :count more | i :count więcej | +| Record action: check again / confirm | `issues.recheck` / `_confirm` | Check this address again / Fetch “:url” again now and update its issues? | Sprawdź ten adres ponownie / Pobrać teraz ponownie „:url” i zaktualizować jego problemy? | +| Check again result | `issues.rechecked` | one: "Checked again: :count issue remains for this address." other: "Checked again: :count issues remain for this address." | one: "Sprawdzono ponownie: dla tego adresu został :count problem." few: "Sprawdzono ponownie: dla tego adresu zostały :count problemy." many: "Sprawdzono ponownie: dla tego adresu zostało :count problemów." other: "Sprawdzono ponownie: dla tego adresu zostało :count problemu." | +| Check again: fixed | `issues.recheck_fixed` | Checked again: no issues remain for this address. | Sprawdzono ponownie: dla tego adresu nie ma już problemów. | +| Record action: ignore / confirm | `issues.ignore` / `_confirm` | Ignore issue / Ignore this issue? It stays hidden in later crawls until you stop ignoring it. | Ignoruj problem / Zignorować ten problem? Pozostanie ukryty w kolejnych skanowaniach, dopóki nie przestaniesz go ignorować. | +| Bulk action: ignore | `issues.ignore_selected` | Ignore issues | Ignoruj problemy | +| Bulk ignore confirm | `issues.ignore_selected_confirm` | one: "Ignore :count issue? It stays hidden in later crawls." other: "Ignore :count issues? They stay hidden in later crawls." | one: "Zignorować :count problem? Pozostanie ukryty w kolejnych skanowaniach." few: "Zignorować :count problemy? Pozostaną ukryte w kolejnych skanowaniach." many: "Zignorować :count problemów? Pozostaną ukryte w kolejnych skanowaniach." other: "Zignorować :count problemu? Pozostaną ukryte w kolejnych skanowaniach." | +| Record action: stop ignoring / confirm | `issues.unignore` / `_confirm` | Stop ignoring / Show this issue in the audit again? | Przestań ignorować / Ponownie pokazywać ten problem w audycie? | + +**Issue kinds** (`issues.kind_` labels; `issues.sum_` is the one-line Details cell). Severities and fix routes are the defaults. The `tool` of each issue names the fixing MCP tool, a data route (D-14, ROADMAP success criterion 3, RESEARCH Open Question 4). An issue whose cause is template code carries `fix: code` instead. Kinds marked CMS are the Phase 32 CMS-data kinds, now labelled for the admin. + +| Kind | Sev. | Fix | EN label | PL label | Details cell EN / PL | +|------|------|-----|----------|----------|----------------------| +| `page_status` | Error | MCP | Page returns an error | Strona zwraca błąd | Status :status / Kod :status | +| `sitemap_error` | Error | MCP | Sitemap lists a page that does not answer 200 | Mapa witryny zawiera stronę bez kodu 200 | Status :status in :file / Kod :status w :file | +| `sitemap_noindex` | Error | MCP | Sitemap lists a noindex page | Mapa witryny zawiera stronę z noindex | In :file / W :file | +| `sitemap_blocked` | Error | MCP | Sitemap lists an address blocked in robots.txt | Mapa witryny zawiera adres zablokowany w robots.txt | Blocked by line :line / Blokowany przez wiersz :line | +| `sitemap_missing` | Notice | MCP | Indexable page missing from the sitemaps | Indeksowalna strona poza mapami witryny | Found through :count links / Znaleziona przez linki: :count | +| `broken_link` | Error | MCP | Link to a missing page | Link do nieistniejącej strony | From :count pages / Z :count stron | +| `redirected_link` | Notice | MCP | Link to a redirect | Link do przekierowania | Redirects to :target / Przekierowuje na :target | +| `canonical_missing` | Warning | Code | No canonical link | Brak linku kanonicznego | — | +| `canonical_error` | Error | MCP | Canonical points to an error page | Adres kanoniczny wskazuje stronę z błędem | :url answers :status / :url odpowiada kodem :status | +| `canonical_noindex` (CMS) | Error | MCP | Canonical points to a noindex page | Adres kanoniczny wskazuje stronę z noindex | :url | +| `canonical_redirected` (CMS) | Warning | MCP | Canonical points to a redirect | Adres kanoniczny wskazuje przekierowanie | :url → :target | +| `missing_title` (CMS + crawl) | Warning | MCP | Missing title | Brak tytułu | — | +| `duplicate_title` (CMS + crawl) | Warning | MCP | Duplicate title | Powielony tytuł | Same as :count other pages / Taki sam jak na innych stronach: :count | +| `missing_description` (CMS + crawl) | Notice | MCP | Missing meta description | Brak opisu meta | — | +| `duplicate_description` (CMS + crawl) | Notice | MCP | Duplicate meta description | Powielony opis meta | Same as :count other pages / Taki sam jak na innych stronach: :count | +| `rule_overrides_record` (CMS) | Notice | MCP | URL rule overrides the record | Reguła adresu nadpisuje rekord | :fields | +| `redirect_chain` (CMS) | Warning | MCP | Redirect chain | Łańcuch przekierowań | :count hops to :final / Liczba przekierowań: :count, do :final | +| `noindex_blocked` (CMS) | Error | MCP | noindex page blocked in robots.txt | Strona z noindex zablokowana w robots.txt | Line :line / Wiersz :line | +| `h1_missing` | Error | Code | No H1 heading | Brak nagłówka H1 | — | +| `h1_multiple` | Error | MCP or Code | More than one H1 | Więcej niż jeden nagłówek H1 | :count H1 headings / Nagłówki H1: :count | +| `h1_in_content` (CMS, only while Journal's H1 setting is on) | Warning | MCP | H1 in the content | H1 w treści | :count H1 headings / Nagłówki H1: :count | +| `heading_skipped` | Warning | MCP or Code | Skipped heading level | Pominięty poziom nagłówka | H:from → H:to | +| `heading_empty` | Warning | MCP or Code | Empty heading | Pusty nagłówek | H:level | +| `h1_title_mismatch` | Notice | MCP | H1 does not match the title | H1 nie pasuje do tytułu | “:h1” | +| `jsonld_invalid` | Error | MCP or Code | Structured data cannot be read | Nie można odczytać danych strukturalnych | :error | +| `jsonld_multiple` | Warning | Code | More than one JSON-LD graph | Więcej niż jeden graf JSON-LD | :count scripts / Skrypty: :count | +| `jsonld_required` | Error | MCP | Structured data misses a required property | Brak wymaganej właściwości w danych strukturalnych | :type: :property | +| `jsonld_recommended` | Notice | MCP | Structured data misses a recommended property | Brak zalecanej właściwości w danych strukturalnych | :type: :property | +| `hreflang_reciprocal` | Warning | MCP or Code | hreflang has no return link | hreflang bez linku zwrotnego | :locale → :url | +| `hreflang_error` | Error | MCP | hreflang points to an error page | hreflang wskazuje stronę z błędem | :locale → :url (:status) | +| `generic_anchor` | Warning | MCP | Generic link text | Ogólnikowy tekst linku | “:text” → :target / „:text” → :target | +| `image_alt` (CMS + crawl) | Warning | MCP | Image without alt text | Obraz bez tekstu alternatywnego | :count images / Obrazy: :count | +| `image_size` | Notice | Code | Image without width and height | Obraz bez szerokości i wysokości | :count images / Obrazy: :count | +| `featured_lazy` | Notice | Code | Main image loads lazily | Główny obraz ładuje się z opóźnieniem (lazy) | — | +| `content_js` | Warning | Code | Content needs JavaScript | Treść wymaga JavaScriptu | Missing from the raw HTML: :what / Brak w surowym HTML: :what | +| `orphan_page` | Warning | MCP | Orphan page (no internal links) | Strona osierocona (brak linków wewnętrznych) | :count suggestions / Propozycje: :count | + +### Crawls (C24) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| List title / record name | `crawls.list_title` / `crawls.name` | Crawls / Crawl | Skanowania / Skanowanie | +| Columns | `crawls.started_at` / `_status` / `_pages` / `_issues` / `_trigger` / `_duration` | Started / Status / Pages / Issues / Started by / Duration | Rozpoczęto / Stan / Strony / Problemy / Uruchomienie / Czas trwania | +| Status values | `crawls.st_queued` / `_running` / `_finished` / `_failed` / `_cancelled` | Queued / Running / Finished / Failed / Cancelled | W kolejce / W toku / Zakończone / Nieudane / Anulowane | +| Pages cell at limit | `crawls.pages_limit` | :count (limit reached) | :count (osiągnięto limit) | +| Trigger values | `crawls.by_admin` / `_schedule` / `_mcp` | Manual: :name / Schedule / MCP: :client | Ręcznie: :name / Harmonogram / MCP: :client | +| Started (toast) | `crawls.started` | Crawl started. Follow it in SEO › Crawls; the issues appear in Site audit when it finishes. | Rozpoczęto skanowanie. Postęp znajdziesz w SEO › Skanowania; problemy pojawią się w Audycie witryny po jego zakończeniu. | +| Already running (action error) | `crawls.already_running` | A crawl is already running (started :time). Wait for it, or cancel it in SEO › Crawls. | Skanowanie już trwa (rozpoczęte :time). Poczekaj na nie lub anuluj je w SEO › Skanowania. | +| No site address (action error) | `crawls.no_site_url` | The site address for crawls is not configured (golem15.seo.site_url). Ask the administrator to set it. | Adres witryny do skanowania nie jest skonfigurowany (golem15.seo.site_url). Poproś administratora o jego ustawienie. | +| Preview subtitle | `crawls.preview` | Crawl of :url | Skanowanie :url | +| P6 running (plain) | `crawls.running_title` / `crawls.running_text` | In progress / :done pages fetched so far, up to :limit. Reload the page to see the progress. | W toku / Dotąd pobrano stron: :done, najwyżej :limit. Odśwież stronę, aby zobaczyć postęp. | +| P6 queued (plain) | `crawls.queued_text` | Waiting for a free worker. It starts within a minute. | Czeka na wolny proces. Rozpocznie się w ciągu minuty. | +| P6 failed (danger) | `crawls.failed_title` / `crawls.failed_text` | The crawl failed / :error. Pages fetched before the failure and their issues are kept. | Skanowanie nie powiodło się / :error. Strony pobrane przed błędem i ich problemy zostają zachowane. | +| P6 cancelled (plain) | `crawls.cancelled_text` | Cancelled by :name at :time. | Anulowane przez: :name, :time. | +| P6 limit reached (warning) | `crawls.limit_text` | The crawl stopped at the limit of :limit pages. Raise the limit in SEO settings › Site audit to crawl the whole site. | Skanowanie zatrzymało się na limicie :limit stron. Zwiększ limit w Ustawieniach SEO › Audyt witryny, aby przeskanować całą witrynę. | +| P6 staging (plain) | `crawls.staging_text` | On this environment every page carries noindex from the non-production lock; the crawl does not report it. | W tym środowisku każda strona ma noindex z blokady nieprodukcyjnej; skanowanie tego nie zgłasza. | +| P6 stats | `crawls.stat_pages` / `_errors` / `_warnings` / `_notices` / `_duration` | Pages fetched / Errors / Warnings / Notices / Duration | Pobrane strony / Błędy / Ostrzeżenia / Uwagi / Czas trwania | +| P6 kind table columns | `crawls.kinds_problem` / `_severity` / `_count` | Problem / Severity / Addresses | Problem / Waga / Adresy | +| P6 no issues | `crawls.no_issues` | This crawl found no issues. | To skanowanie nie wykryło problemów. | +| Record action: cancel / confirm / done | `crawls.cancel` / `_confirm` / `crawls.cancelled` | Cancel crawl / Cancel this crawl? Pages fetched so far and their issues are kept. / Crawl cancelled. | Anuluj skanowanie / Anulować to skanowanie? Pobrane dotąd strony i ich problemy zostaną zachowane. / Anulowano skanowanie. | +| Delete selected confirm | `crawls.delete_selected_confirm` | one: "Delete :count crawl? Its pages and issues are deleted with it." other: "Delete :count crawls? Their pages and issues are deleted with them." | one: "Usunąć :count skanowanie? Jego strony i problemy zostaną usunięte." few: "Usunąć :count skanowania? Ich strony i problemy zostaną usunięte." many: "Usunąć :count skanowań? Ich strony i problemy zostaną usunięte." other: "Usunąć :count skanowania? Ich strony i problemy zostaną usunięte." | +| Running crawl delete refused (403 Message) | `crawls.delete_running` | A running crawl cannot be deleted. Cancel it first. | Nie można usunąć trwającego skanowania. Najpierw je anuluj. | + +### robots.txt screen, AI bots tab additions (C25) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| llms.txt switch | `robots.llms_enabled` | Serve llms.txt | Udostępniaj llms.txt | +| llms.txt comment | `robots.llms_enabled_comment` | Lists the site's indexable pages from the sitemaps at /llms.txt, for AI tools that read it. | Wypisuje indeksowalne strony witryny z map witryny pod /llms.txt, dla narzędzi AI, które go czytają. | +| Introduction | `robots.llms_intro` | Introduction | Wstęp | +| Introduction comment | `robots.llms_intro_comment` | A short description of the site at the top of llms.txt. Empty: the default meta description. | Krótki opis witryny na początku llms.txt. Puste: domyślny opis meta. | + +### Sitemap settings (`golem15.sitemap::lang.*`, C26) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Settings tile label / description (replaces both Phase 17 tiles) | `sitemap:settings.label` / `_description` | Sitemaps / XML sitemaps per content type, images, News, IndexNow and the HTML sitemap. | Mapy witryny / Mapy XML według typów treści, obrazy, News, IndexNow i mapa HTML. | +| Tabs | `sitemap:settings.tab_files` / `_types` / `_extra` / `_media` / `_indexnow` / `_html` | Files / Content types / Additional addresses / Images and News / IndexNow / HTML sitemap | Pliki / Typy treści / Dodatkowe adresy / Obrazy i News / IndexNow / Mapa HTML | +| Site address / comment | `sitemap:settings.site_url` / `_comment` | Site address / The public address of the site, e.g. https://www.example.com. Every address in the sitemaps starts with it. | Adres witryny / Publiczny adres witryny, np. https://www.example.com. Od niego zaczyna się każdy adres w mapach. | +| File size / comment | `sitemap:settings.file_size` / `_comment` | Addresses per file / 1000 by default, at most 50 000. Smaller files are easier to monitor one by one in Google Search Console. A change renumbers every file. | Adresów w pliku / Domyślnie 1000, najwyżej 50 000. Mniejsze pliki łatwiej monitorować pojedynczo w Google Search Console. Zmiana przenumerowuje wszystkie pliki. | +| S1 label | `sitemap:files.label` | Files | Pliki | +| S1 columns | `sitemap:files.file` / `_urls` / `_lastmod` | File / Addresses / Last modified | Plik / Adresy / Ostatnia zmiana | +| S1 left-out heading / columns | `sitemap:files.left_out` / `_reason` / `_count` | Left out of the sitemaps / Reason / Addresses | Pominięte w mapach witryny / Powód / Adresy | +| S1 reasons | `sitemap:files.reason_noindex` / `_canonical` / `_redirect` / `_type` | noindex / Canonical URL points elsewhere / Address redirects / Content type turned off | noindex / Adres kanoniczny wskazuje inny adres / Adres przekierowuje / Wyłączony typ treści | +| S1 note | `sitemap:files.index_note` | Search engines read the index at :url, and robots.txt points to it. The files are rebuilt after content changes and every night; saving this form rebuilds them now. | Wyszukiwarki czytają indeks pod :url, a robots.txt go wskazuje. Pliki są odbudowywane po zmianach treści i co noc; zapisanie tego formularza odbudowuje je od razu. | +| S1 empty | `sitemap:files.empty` | No files yet. They are built within a few minutes after the first save. | Nie ma jeszcze plików. Powstaną w ciągu kilku minut po pierwszym zapisie. | +| S1 rebuilding | `sitemap:files.building` | Rebuilding now. Reload the page in a minute. | Trwa odbudowa. Odśwież stronę za minutę. | +| Per type: include | `sitemap:settings.type_include` | Include :type | Uwzględniaj: :type | +| Per type: file name / comment | `sitemap:settings.type_slug` / `_comment` | File name for :type / Files are named sitemap--1.xml, sitemap--2.xml and so on. | Nazwa pliku dla: :type / Pliki nazywają się sitemap--1.xml, sitemap--2.xml i tak dalej. | +| Per type: HTML heading | `sitemap:settings.type_title` | Heading of :type in the HTML sitemap | Nagłówek grupy :type w mapie HTML | +| Additional addresses / add | `sitemap:settings.extra_urls` / `_add` | Additional addresses / Add address | Dodatkowe adresy / Dodaj adres | +| Additional comment | `sitemap:settings.extra_urls_comment` | Addresses that no content type lists, e.g. the site's static pages. They go into the sitemap-pages files. | Adresy, których nie podaje żaden typ treści, np. statyczne strony witryny. Trafiają do plików sitemap-pages. | +| Row: address / last modified | `sitemap:settings.extra_url` / `_lastmod` | Address / Last modified | Adres / Ostatnia zmiana | +| Image sitemap / comment | `sitemap:settings.images_enabled` / `_comment` | Image sitemap / Lists the featured image and the images in the content of every listed page, in the sitemap-images files. | Mapa obrazów / Wypisuje obraz wyróżniający i obrazy w treści każdej podanej strony, w plikach sitemap-images. | +| News sitemap / comment | `sitemap:settings.news_enabled` / `_comment` | Google News sitemap / Only for sites accepted in Google News. Lists articles published in the last two days. | Mapa Google News / Tylko dla witryn przyjętych do Google News. Wypisuje artykuły opublikowane w ciągu ostatnich dwóch dni. | +| Publication / comment | `sitemap:settings.news_publication` / `_comment` | Publication name / As registered in Google News. Empty: the site name. | Nazwa publikacji / Tak jak w Google News. Puste: nazwa witryny. | +| News sources / comment | `sitemap:settings.news_sources` / `_comment` | Types and categories / Articles of these types or categories go into the News sitemap. Empty: every article type. | Typy i kategorie / Artykuły tych typów lub kategorii trafiają do mapy News. Puste: każdy typ artykułów. | +| IndexNow / comment | `sitemap:settings.indexnow_enabled` / `_comment` | IndexNow / Tells Bing, Yandex and other IndexNow engines about new, changed and deleted addresses right away. Google does not use IndexNow. | IndexNow / Od razu informuje Bing, Yandex i inne wyszukiwarki IndexNow o nowych, zmienionych i usuniętych adresach. Google nie korzysta z IndexNow. | +| New key / comment | `sitemap:settings.indexnow_new_key` / `_comment` | Generate a new key on save / The old key file stops working at once. Use it only when the key leaked. | Wygeneruj nowy klucz przy zapisie / Stary plik klucza od razu przestaje działać. Używaj tylko, gdy klucz wyciekł. | +| S2 key | `sitemap:indexnow.key` | Key: :key, served at :url | Klucz: :key, udostępniany pod :url | +| S2 no key | `sitemap:indexnow.no_key` | A key is generated when you turn IndexNow on and save. | Klucz zostanie wygenerowany po włączeniu IndexNow i zapisie. | +| S2 last ping | `sitemap:indexnow.last` | Last notification: :time, :count addresses, answer :status. | Ostatnie powiadomienie: :time, adresy: :count, odpowiedź :status. | +| S2 never | `sitemap:indexnow.never` | No notification sent yet. | Nie wysłano jeszcze powiadomienia. | +| S2 403 (warning) | `sitemap:indexnow.err_key` | The engines could not read the key file at :url. Check that the site serves it. | Wyszukiwarki nie mogły odczytać pliku klucza pod :url. Sprawdź, czy witryna go udostępnia. | +| S2 422 (warning) | `sitemap:indexnow.err_host` | The engines refused the addresses because they do not match the site address. | Wyszukiwarki odrzuciły adresy, bo nie pasują do adresu witryny. | +| S2 429 (plain) | `sitemap:indexnow.err_rate` | Too many notifications; the next attempt is at :time. | Zbyt wiele powiadomień; następna próba: :time. | +| HTML sitemap / comment | `sitemap:settings.html_enabled` / `_comment` | HTML sitemap / A page for visitors that lists every indexable address, linked from the site footer. | Mapa witryny HTML / Strona dla odwiedzających z listą wszystkich indeksowalnych adresów, linkowana ze stopki witryny. | +| HTML address / comment | `sitemap:settings.html_path` / `_comment` | Address / The site's sitemap page uses this address. Change it in the site as well. | Adres / Strona mapy witryny używa tego adresu. Zmień go także w witrynie. | +| Links per page / comment | `sitemap:settings.html_page_size` / `_comment` | Links per page / Larger groups are split into numbered pages. Default 150. | Linków na stronie / Większe grupy są dzielone na numerowane strony. Domyślnie 150. | +| File size invalid (422) | `sitemap:errors.file_size` | Enter a whole number from 1 to 50 000. | Wpisz liczbę całkowitą od 1 do 50 000. | +| File name invalid (422) | `sitemap:errors.slug` | Use lowercase letters, digits and hyphens, e.g. posts. | Użyj małych liter, cyfr i łączników, np. posts. | +| File name taken (422) | `sitemap:errors.slug_taken` | Another content type already uses this file name. | Inny typ treści już używa tej nazwy pliku. | +| HTML address invalid (422) | `sitemap:errors.html_path` | Start the address with / and use a path only, e.g. /mapa-strony. | Zacznij adres od / i użyj samej ścieżki, np. /mapa-strony. | +| Page size invalid (422) | `sitemap:errors.page_size` | Enter a whole number from 50 to 500. | Wpisz liczbę całkowitą od 50 do 500. | +| Site address invalid (422) | existing `sitemap:errors.frontend_url` | unchanged | unchanged | + +### Journal settings, save notice and console (C27) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| New tab | `journal:journal.tab_headings` | Headings and links | Nagłówki i linki | +| H1 setting | `journal:journal.h1_to_h2_label` | Convert H1 to H2 in post content | Zamieniaj H1 na H2 w treści wpisów | +| H1 setting comment | `journal:journal.h1_to_h2_comment` | Turn it off only when the page template does not show the post title as the H1. On: the editor offers H2–H4, and every H1 saved through the admin, the API, MCP or an import becomes an H2. Off: content keeps its H1 headings, and the site shows them unchanged. | Wyłącz tylko wtedy, gdy szablon strony nie wyświetla tytułu wpisu jako H1. Włączone: edytor oferuje H2–H4, a każdy H1 zapisany przez panel, API, MCP lub import staje się H2. Wyłączone: treść zachowuje nagłówki H1, a witryna wyświetla je bez zmian. | +| Generic anchors | `journal:journal.generic_anchors_label` | Generic link texts | Ogólnikowe teksty linków | +| Generic anchors comment | `journal:journal.generic_anchors_comment` | One per line. The editor warns when the whole text of a link is one of these. | Jeden w wierszu. Edytor ostrzega, gdy cały tekst linku jest jednym z nich. | +| Generic anchors default | (seed value) | click here, here, more, read more, learn more, this link, link | kliknij tutaj, tutaj, tu, więcej, czytaj więcej, czytaj dalej, ten link, link | +| H1 conversion save notice (on the editor field) | `journal:editor.h1_converted` | one: ":count H1 heading was converted to H2, because the page shows the post title as the H1." other: ":count H1 headings were converted to H2, because the page shows the post title as the H1." | one: "Zamieniono :count nagłówek H1 na H2, bo strona wyświetla tytuł wpisu jako H1." few: "Zamieniono :count nagłówki H1 na H2, bo strona wyświetla tytuł wpisu jako H1." many: "Zamieniono :count nagłówków H1 na H2, bo strona wyświetla tytuł wpisu jako H1." other: "Zamieniono :count nagłówka H1 na H2, bo strona wyświetla tytuł wpisu jako H1." | +| API/MCP answer field note | `journal:editor.h1_converted` (same phrase, in the response `notices`) | as above | as above | + +Console output of `journal:fix-headings` is English only, like every SummerCMS command: +- Dry run (default): `Would convert :count H1 headings in :posts posts. Run with --apply to save.` +- Applied: `Converted :count H1 headings in :posts posts.` +- Nothing to do: `No H1 headings found in post content.` +- Refused (exit code 1): `H1 to H2 conversion is turned off in the Journal settings (Headings and links). Turn it on first, or keep the H1 headings.` + +### Blog Editor additions (`sm-journal-plugin/assets/src/blogeditor/labels.ts`, C28) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| H1 button (setting off) | `heading1` | Heading 1 | Nagłówek 1 | +| Outline toggle | `outlineShow` / `outlineHide` | Show outline / Hide outline | Pokaż konspekt / Ukryj konspekt | +| Outline name | `outline` | Outline | Konspekt | +| Heading count | `outlineCount` | one: "{count} heading" other: "{count} headings" | one: "{count} nagłówek" few: "{count} nagłówki" many: "{count} nagłówków" other: "{count} nagłówka" | +| Problem count | `outlineProblems` | one: "{count} problem" other: "{count} problems" | one: "{count} problem" few: "{count} problemy" many: "{count} problemów" other: "{count} problemu" | +| No headings | `outlineEmpty` | No headings yet. Headings split the text into sections for readers and search engines. | Brak nagłówków. Nagłówki dzielą tekst na części dla czytelników i wyszukiwarek. | +| Flag: skipped | `flagSkipped` | Skipped level: H{from} → H{to} | Pominięty poziom: H{from} → H{to} | +| Flag: empty | `flagEmpty` | Empty heading | Pusty nagłówek | +| Flag: only a link | `flagLinkOnly` | Heading is only a link | Nagłówek jest tylko linkiem | +| Flag: H1 (setting on) | `flagH1` | H1 in the text: saved as H2 | H1 w tekście: zostanie zapisany jako H2 | +| Links subsection | `outlineLinks` | Links | Linki | +| Flag: generic link | `flagGeneric` | Link text “{text}” does not say where it leads | Tekst linku „{text}” nie mówi, dokąd prowadzi | +| Rel group label | `linkRel` | Link attributes | Atrybuty linku | +| Rel options | `relNofollow` / `relSponsored` / `relUgc` | nofollow / sponsored (paid) / ugc (user content) | nofollow / sponsored (płatny) / ugc (od użytkowników) | +| Rel hint | `relHint` | Only for links to other sites. Links within the site never get these. | Tylko dla linków do innych witryn. Linki w obrębie witryny nigdy ich nie dostają. | +| Generic-anchor warning | `genericWarning` | “{text}” does not say where the link leads. Use words that describe the target page. | „{text}” nie mówi, dokąd prowadzi link. Użyj słów opisujących stronę docelową. | +| File name / hint | `fileName` / `fileNameHint` | File name / Lowercase words joined with hyphens, e.g. lion-s-mane-in-the-forest. | Nazwa pliku / Małe litery i łączniki, np. lwia-grzywa-w-lesie. | +| Decorative | `decorative` | Decorative image (no alt text) | Obraz dekoracyjny (bez tekstu alternatywnego) | +| Alt required (replaces the existing text) | `altRequired` | Enter the alt text, or mark the image as decorative. | Wpisz tekst alternatywny lub oznacz obraz jako dekoracyjny. | + +### Public layer dictionary (`vue-seo-layer`, L8) + +| Element | Key | EN | PL | +|---------|-----|----|----| +| Breadcrumbs `aria-label` | `breadcrumbs.label` | Breadcrumbs | Ścieżka nawigacji | +| Pagination `aria-label` | `pagination.label` | Pagination | Stronicowanie | +| Previous / next | `pagination.previous` / `pagination.next` | Previous page / Next page | Poprzednia strona / Następna strona | +| Number link `aria-label` | `pagination.page` | Page {page} | Strona {page} | +| HTML sitemap H1 | `sitemap.title` | Sitemap | Mapa strony | +| Group page H1 | `sitemap.groupTitle` | Sitemap: {group} | Mapa strony: {group} | +| Page-number suffix on group page 2+ | `sitemap.pageSuffix` | Page {page} | Strona {page} | +| Link to a paged group | `sitemap.all` | {group}: all ({count}) | {group}: wszystkie ({count}) | +| Nothing to list | `sitemap.empty` | There are no pages to list yet. | Nie ma jeszcze stron do wyświetlenia. | +| Footer link | `sitemapLink` | Sitemap | Mapa strony | +| New-tab hint (visually hidden, inside external links; rendered only while `links_new_tab` is on) | `links.newTab` | (opens in a new tab) | (otwiera się w nowej karcie) | + +Breadcrumb item names, the home crumb name (`settings.breadcrumb_home`) and the HTML sitemap group headings (`sitemap:settings.type_title`) come from the CMS in the page's content locale. The layer never translates them. The journal strings (`journal.pagination.*`, `journal.post.related`) stay in the journal layer's i18n files. `journal.readMore` becomes unused (L5) and is kept for one release, since apps may reference it. + +### Destructive actions + +| Action | Confirmation approach | Copy | +|--------|----------------------|------| +| Delete a registry page / selected pages | Existing danger confirm | `pages.delete_confirm`, `pages.delete_selected_confirm` | +| Delete auto-links | Existing danger confirm | `autolinks.delete_confirm`, `autolinks.delete_selected_confirm` | +| Delete crawls | Existing danger confirm (toolbar delete). A running crawl refuses with 403 | `crawls.delete_selected_confirm`, `crawls.delete_running` | +| Cancel a running crawl | Record action, non-danger confirm (pages and issues fetched so far are kept) | `crawls.cancel_confirm` | +| Ignore issues (record and bulk) | Non-danger confirm (reversible through "Stop ignoring") | `issues.ignore_confirm`, `issues.ignore_selected_confirm` | +| Generate a new IndexNow key | Checkbox plus Save; the comment states that the old key stops working at once. No dialog | `sitemap:settings.indexnow_new_key_comment` | +| Change the sitemap file size | No dialog; the comment states that every file is renumbered | `sitemap:settings.file_size_comment` | +| Change the page address form | No dialog; the comment states the 301 consequence | `settings.page_form_comment` | +| Turn Journal's H1 conversion off or on | No dialog; the comment states both modes. Already stored content is not rewritten by the switch (only by `journal:fix-headings --apply`) | `journal:journal.h1_to_h2_comment` | +| `journal:fix-headings --apply` | CLI only, dry run by default | console copy above | +| Add focus keyphrases | No confirm (a toolbar action creates rows that can be deleted). The result toast states the count | `autolinks.keyphrases_added`, `autolinks.keyphrases_none` | + +--- + +## Component Contracts + +**Focal points:** +- Site audit: the issue table, sorted with errors first. +- Crawls: the newest run row. +- Crawl preview: the P6 stats strip. +- Issue preview: P7's "How to fix" callout. +- Pages and Auto-links: the list table, with the toolbar create. +- SEO tab: unchanged (E3 at the top). The new structured-data block is reached by scrolling, with E8's type select as its anchor. +- SEO settings: the active tab's first field. +- Sitemap settings: the S1 file table. +- Blog Editor: the content area. The outline bar sits under it and never takes focus on load. +- Public breadcrumbs: secondary, above the H1, in small muted text. +- Public pagination: below the listing. +- HTML sitemap: its H1 and the first group. + +### C15. SEO navigation + +- `pact.NavigationItem` `seo` keeps its label, icon, permission, order 600 and landing controller (Redirects). The side menu, in this order: + 1. Redirects (`arrow-right`) + 2. 404 log (`triangle-alert`) + 3. URL rules (`link`) + 4. **Pages** (`files`) + 5. **Auto-links** (`wand-sparkles`) + 6. **Site audit** (`gauge`) + 7. **Crawls** (`globe`) + 8. robots.txt (`file-text`) + 9. History (`clock`) + 10. Import and export (`download`) +- Every new item requires `golem15.seo.manage` (D-15). An admin without it sees none of them. + +### C16. SEO tab additions (D-44, D-46, D-82, D-86) + +The fields are inserted into `seoTabYAML` through the reserved anchors (32-UI-SPEC C3). The final order: + +| # | Field | Type | Span | Context | Notes | +|---|-------|------|------|---------|-------| +| 1–7 | `seo_status` … `seo_robots` | unchanged | | | P1 gains the callouts `status.primary_gone` (warning) and `status.excluded_*` (plain, title `status.excluded_title`), after the Phase 32 callouts and at most one of each | +| 8 | `seo_primary_parent` | dropdown | full | create, update | **Only for content types whose descriptor supplies `Parents`** (Journal posts). Options: the record's candidate parents in descriptor order; `emptyOption: field.primary_category_default`. Label, comment per copy. An empty value means the first parent | +| 9–11 | `seo_og_title`, `seo_og_description`, `seo_og_image` | unchanged | | | | +| 12 | `seo_schema` | `seoschema` (E8) | full | create, update | Option `kind` = the descriptor `Kind` (`article`, `page`, `collection`, `profile`). Label `field.schema`, comment `field.schema_comment` | +| 13 | `seo_graph` | widget E9 | full | update | `action: seo_graph`, `fill: [seo_schema]`. Label `field.graph` | +| 14 | `seo_links` | widget E10 | full | update | `action: seo_links`. Label `field.links`. Omitted for types whose descriptor supplies no body (`Words` null) | +| 15 | `seo_effective` | partial P2 | full | update | unchanged; P2 gains rows "Schema type" (`effective.schema`: Schema type / Typ schema) and "Breadcrumb trail" (`effective.trail`: Breadcrumb trail / Ścieżka nawigacji) | + +- Permission and the per-type "Show the SEO tab" switch remove the new fields together with the tab (32-UI-SPEC C1). +- The Pages registry form gets the same SEO tab ("Basic | SEO"), because the registry is a signpost content type (D-33). + +### C17. E8 `golem15-seo-schema` (field type `seoschema`) + +- **Value:** a JSON **string**, so that E9 can read it through `fill`, which passes scalars only: `{"type":""|"", "fields":{...}, "custom":""}`. The empty string means the default type, no fields and no custom JSON-LD. `Decode` normalises the value: it drops `fields` keys of other types and trims `custom`. +- **Layout** (sections stacked with a 24px gap): + 1. **Type row.** + - A 44px select, min-width 280px, labelled `type`. Its options depend on `kind`: + - `article`: Default, BlogPosting, Article, NewsArticle; + - `page`: Default, WebPage, AboutPage, ContactPage, Service; + - `collection` and `profile`: a single fixed type, shown as read-only text with no select. + - The default option reads `typeDefault` with the kind's default type: BlogPosting for article; WebPage for page, or Service for registry kinds offer and service; CollectionPage; ProfilePage. + - Under the select, 8px below, comes the 13px muted note for the effective type (`noteArticle`, `notePage`, `noteContact`, `noteService`, `noteCollection` or `noteProfile`). + 2. **Type fields**, only for Service: a two-column grid (16px gap, one column under 600px): + - `serviceType` (44px text); + - `areaServed` (44px text, hint); + - `price` (44px text, `inputmode="decimal"`, a comma or a dot as the decimal separator); + - `currency` (44px text, 3 letters, uppercased on blur; default `PLN` when the default content locale is `pl`, otherwise empty). + - An invalid price or currency shows a 13px `--c-danger` line (`priceInvalid`, `currencyInvalid`) under its input. The element keeps the last valid value. + 3. **Custom JSON-LD.** + - A disclosure button `custom` (14px/600, `chevron-down` rotating 180° with no animation, `aria-expanded`), followed by the muted summary `customSet` when the value is set. It is open on load when set. + - Inside the disclosure: + - the hint (13px muted); + - a `