D-01/D-02/D-03/D-04: implementation is derived only from the read-only PHP tree at SHA 725d547ec839f02b5fdc0f0a6faaed601a414d50; no PHP file changes.
D-10: gormigrate creates final `winter_translate_locales`, `winter_translate_attributes`, `winter_translate_indexes`, and empty `winter_translate_messages`; it creates no rainlab-era or `golem15_translate_*` tables.
D-08: an idempotent seed creates enabled `en` as default at sort_order 1 and enabled non-default `pl` as Polski at sort_order 2, and never seeds `de`.
D-07/D-12: a backpack-published resolver selects URL prefix, valid user preferred_locale, remembered `golem15.translate.locale`, cookie-gated Accept-Language, then default, and surf stores only a validated code on context.
KERN-07: no package-level mutable current locale exists; `Translator.Locale(ctx)` and `towel.WithLocale` carry request state.
D-15/D-16: the new module is `git.golem15.com/golem15/sm-translate-plugin`, package `translate`, plugin ID `golem15.translate`, in the intended sibling checkout.
Boot publishes the framework interface into backpack
LocaleResolver
from
to
via
pattern
modules/surf/router.go
modules/towel/context.go
validated resolver result is written with towel.WithLocale
WithLocale
from
to
via
pattern
../sm-translate-plugin/classes/translator.go
../sm-translate-plugin/models/locale.go
every candidate is checked against enabled Locale rows
is_enabled
Create the translate plugin repository, exact Winter-compatible schema and seed, Locale model, context-safe Translator, and optional surf resolver seam.
Purpose: prove one real request locale path from a mounted compiled plugin through Postgres-backed enabled locales into towel.WithLocale before adding the broader model/admin surface.
Output: a compiling sibling plugin and framework seam, with no new dependencies and no Messages admin.
Phase 14.2.1 has no mapped REQUIREMENTS.md IDs, so requirement probing is visibly skipped. This plan uses D-01/D-02/D-03/D-04/D-07/D-08/D-10/D-12/D-15/D-16 and the RESEARCH validation rows as its acceptance contract.
models.Locale, model and migration registries, five gormigrate entries, and exact winter_translate_* tables.
classes.Translator, Translator.Locale(context.Context), and a request resolver implementing surf's LocaleResolver.
Surf optional resolver seam with unchanged fallback behavior when the translate plugin is absent.
Application-neutral plugin and surf README updates for exported contracts.
Task 1: Create the public Gitea remote and authorize local repository setup
../sm-translate-plugin/
.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md (D-15/D-16 and Remotes), .planning/notes/core-plugins-own-repos.md
The user creates the public empty Gitea repository `git@git.golem15.com:golem15/sm-translate-plugin.git`, because this account-level operation is the only non-automatable prerequisite. After the user resumes, verify it with `git ls-remote`, then create or clone `/media/nvme/dev/golem15/summercms.io/summercms/sm-translate-plugin`, initialize `main` as required by the server, and set origin. Also clone the already-existing `sm-grzybyfunkcjonalne-app` remote into `/media/nvme/dev/golem15/summercms.io/summercms/sm-grzybyfunkcjonalne-app`; leave its source layout empty until the D-13 decision gate in Plan 03.
Create one empty public repository in Gitea with owner `golem15` and name `sm-translate-plugin`; do not add generated README, license, or gitignore. Return here after Gitea displays the SSH URL.
The executor runs `git ls-remote`, clones/initializes both local checkouts, and verifies their origin URLs before continuing.
git ls-remote git@git.golem15.com:golem15/sm-translate-plugin.git
Non-zero exit, authentication denial, or repository-not-found text.
- The remote is public and reachable at the exact D-15 SSH URL.
- `git -C ../sm-translate-plugin remote get-url origin` prints `git@git.golem15.com:golem15/sm-translate-plugin.git`.
- The local checkout is the D-16 sibling path, not an install/runtime mirror.
- `git -C ../sm-grzybyfunkcjonalne-app rev-parse --is-inside-work-tree` prints `true`, and its origin names the already-created proof-host remote; no submodule layout has been committed yet.
The intended tracked plugin repository exists locally and remotely, ready for source commits.
Reply `remote-created` after the empty public repository exists.
Task 2: Confirm the one-way Winter table contract
../sm-translate-plugin/updates/
.planning/phases/14.2.1-translate-plugin/14.2.1-CONTEXT.md (D-10), .planning/phases/14.2.1-translate-plugin/14.2.1-RESEARCH.md (Tables, columns, indexes), /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/updates/version.yaml, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/models/Locale.php
Record the selected schema contract before migrations are implemented. Research resolved D-10's provisional prefix against the frozen source: the only compatible selection is the final `winter_translate_*` table family. This gate records the one-way import/storage choice; it does not reopen the PHP pin or permit a JSON-column alternative.
Which persistent table contract should this shared plugin publish?
Once released and imported by Journal, renaming these tables requires coordinated data migrations in every host. The frozen PHP pin explicitly reads `winter_translate_*` after its RainLab rename history.
Squash directly to the four final winter_translate_* tables
Matches SHA 725d547, Winter imports, and Journal cutover.
The names become a shared persistent contract.
Use provisional golem15_translate_* names
Vendor-style naming.
Contradicts the frozen source and breaks direct imports.
Store translations on host-model JSON columns
Fewer tables.
Contradicts D-10 and PHP storage semantics.
test -f /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/models/Locale.php && git -C /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate rev-parse HEAD
Non-zero exit or the printed SHA is not `725d547ec839f02b5fdc0f0a6faaed601a414d50`.
- The response is recorded in the summary.
- Task 3 starts only for `winter-final`; either alternative stops execution as a locked-decision conflict.
Select `winter-final` to implement the locked/researched contract, or select an alternative to stop.
Task 3: Resolve one URL-prefixed request through the plugin, enabled Locale row, surf, and context
The four table names and columns become the import target; changing them after release requires data migrations in every host.
Tasks 1 and 2 completed, with schema option `winter-final`.
../sm-translate-plugin/go.mod, ../sm-translate-plugin/plugin.go, ../sm-translate-plugin/README.md, ../sm-translate-plugin/config/config.yaml, ../sm-translate-plugin/models/registry.go, ../sm-translate-plugin/models/locale.go, ../sm-translate-plugin/updates/registry.go, ../sm-translate-plugin/updates/202610060001_create_winter_translate_locales.go, ../sm-translate-plugin/updates/202610060002_create_winter_translate_attributes.go, ../sm-translate-plugin/updates/202610060003_create_winter_translate_indexes.go, ../sm-translate-plugin/updates/202610060004_create_winter_translate_messages.go, ../sm-translate-plugin/updates/202610060005_seed_en_pl_locales.go, ../sm-translate-plugin/classes/translator.go, modules/surf/locale_resolver.go, modules/surf/router.go, modules/surf/README.md
../fonoteka.go/plugins/golem15/user/go.mod, ../fonoteka.go/plugins/golem15/user/plugin.go, ../fonoteka.go/plugins/golem15/user/models/registry.go, ../fonoteka.go/plugins/golem15/user/updates/registry.go, ../fonoteka.go/plugins/golem15/user/updates/00_base.go, modules/backpack/app.go, modules/surf/router.go, modules/surf/locale_from_principal.go, modules/towel/context.go, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/Translator.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/LocaleMiddleware.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/classes/ApiLocaleMiddleware.php, /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate/config/config.php, .planning/phases/14.2.1-translate-plugin/14.2.1-PATTERNS.md
Implement the production tracer and the complete Plan 01 scope.
Create the Go 1.27 module using only existing GORM/gormigrate/framework dependencies. Register the compiled plugin in init, expose config/models/migrations, and publish a framework-owned surf.LocaleResolver in Boot. Keep Requires() empty so user-preference lookup is optional.
Implement exact squashed DDL for winter_translate_locales, winter_translate_attributes, winter_translate_indexes, and winter_translate_messages, including all final columns and PHP-indexed columns from RESEARCH. Messages is DDL only and remains empty. Use explicit gormigrate up/down; never AutoMigrate and never create rainlab or provisional-prefixed tables. Seed only en/pl idempotently with D-08 values.
Implement Locale with no timestamps, table name, code/name rules, and Fillable limited to code/name/is_enabled. Implement Translator/Resolver with the exact D-07 order: enabled URL first segment; valid principal preferred_locale; remembered golem15.translate.locale; Accept-Language only when browser detection is enabled and locale_manually_set is absent; enabled default. The manual cookie is a flag with value 1, never a locale. Validate every candidate against enabled rows. URL-prefix handling strips only a valid enabled prefix before routing and sets the manual flag using configured expiry. Provide the API resolver variant preferred → Accept-Language → default without persistence. All current-locale access takes context.
Add the surf interface and optional lookup without importing the plugin. When no resolver is published, retain existing Accept-Language behavior and principal overlay so hosts without translate do not regress. Update surf README for the exported seam and the new plugin README using only neutral host-application examples.
go -C ../sm-translate-plugin vet ./... && go -C ../sm-translate-plugin test ./... -short -count=1 && go vet ./modules/surf/... && go test ./modules/surf -short -count=1
<fails_when>Non-zero exit, any package reports build failed, or either test command reports FAIL.</fails_when>
<acceptance_criteria>
- All four migration files contain their exact winter_translate_* table name; no source file contains golem15_translate_ or rainlab_translate_.
- Seed source contains en and pl with en default/enabled and pl enabled/non-default, and contains no Deutsch seed.
- Plugin module/ID/package match D-15 and plugin Boot publishes the framework resolver interface.
- Resolver order is URL → principal → remembered locale → cookie-gated Accept-Language → default.
- locale_manually_set is treated only as a flag; locale candidates are checked against enabled rows.
- There is no mutable package-level request locale and surf writes the selected code with towel.WithLocale.
- PHP source tree has no diff.
</acceptance_criteria>
A production URL-prefix request resolves through a mounted compiled plugin to a validated locale on context, while the schema and en/pl rows are ready for later model/admin slices.
<threat_model>
Trust Boundaries
Boundary
Description
HTTP URL/header/cookies → Translator
Untrusted locale candidates enter request context
Plugin → surf service registry
Optional compiled plugin supplies a framework interface
gormigrate → Postgres
Persistent import-compatible schema is created
STRIDE Threat Register
Threat ID
Category
Component
Severity
Disposition
Mitigation Plan
T-14.2.1-01
Tampering
Translator locale inputs
high
mitigate
Validate every URL, principal, remembered, and header candidate against enabled Locale rows
T-14.2.1-02
Information Disclosure
request locale state
high
mitigate
Context-only locale; no process singleton; concurrent-request test in Plan 04
T-14.2.1-03
Tampering
manual-selection cookie
medium
mitigate
Treat locale_manually_set as flag-only and revalidate remembered locale
T-14.2.1-04
Tampering
schema source
high
mitigate
Explicit gormigrate DDL from frozen SHA; no AutoMigrate or alternate prefixes
T-14.2.1-SC
Tampering
package installs
high
mitigate
No new external package installation in this plan
ASVS L1: all high threats are blocked by implementation requirements and become fail-closed tests in Plan 04.
</threat_model>
Run the Task 3 command. Confirm `git diff -- /media/nvme/dev/golem15/fonoteka/plugins/golem15/translate` is empty and `git ls-files` in the new plugin lists every created source file.
<success_criteria>
The plugin and surf packages compile and pass short tests.
Exact final tables and en/pl seed are implemented through gormigrate.
Full resolver order is implemented without process-wide locale state.
No deferred Messages UI, CMS components, AI/theme commands, import/export, or PHP edits are introduced.
</success_criteria>
Create `.planning/phases/14.2.1-translate-plugin/14.2.1-01-SUMMARY.md` when done.