From bb903a68509159e90dac6a77a0124d2b2dd14843 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Tue, 6 Oct 2026 18:29:20 +0200 Subject: [PATCH] feat(15-01): add journal en/pl phrasebooks and plugin README Operators get UI catalogs for plugin, journal, post, category, and tag keys, plus an application-neutral README for the compiled plugin. Co-authored-by: Cursor --- README.md | 113 ++++++++++++++++++++++++++++++++++++++++++ lang/en/lang.yaml | 122 ++++++++++++++++++++++++++++++++++++++++++++++ lang/pl/lang.yaml | 74 ++++++++++++++++++++++++++++ plugin.go | 9 ++++ 4 files changed, 318 insertions(+) create mode 100644 README.md create mode 100644 lang/en/lang.yaml create mode 100644 lang/pl/lang.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac799fc --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# golem15.journal + +The compiled journal plugin for SummerCMS applications: translatable posts and categories, tags, a settings singleton, and the tables a host migrate creates for a blog. + +```go +import _ "git.golem15.com/golem15/sm-journal-plugin" +``` + +Module `git.golem15.com/golem15/sm-journal-plugin`, package `journal`, plugin ID `golem15.journal`. + +## Overview + +The plugin is a compiled SummerCMS plugin. Its `init` registers it with the framework, so an application loads it by listing it in `summer.yaml`; `summer build` then emits the blank import above into the application's `plugins.gen.go`. + +It requires `golem15.translate`. Post and Category store default-locale values on host columns and other locales in `golem15_translate_attributes`. `MorphName` is the PHP class string (`Golem15\Journal\Models\Post`, `Golem15\Journal\Models\Category`); it is never `reflect.Type.String()`. + +WinterCMS theme components such as `journalPost` and `journalPosts` are a later successor, not implemented here. + +## Features + +- **Posts.** `models.Post` maps `golem15_journal_posts`. Translatable fields are title, content, content_html, excerpt, metadata, and slug (indexed). Fillable does not include `redactor_id`, `content_html`, or `user_id`. Attachments `featured_images` and `content_images` use `system_files` under the same MorphName. +- **Categories and tags.** `models.Category` is translatable (name, description, slug). Fillable is name, slug, code, description, parent_id; nest_* columns exist in DDL and are not fillable. `models.Tag` is not translatable. Fillable is name, slug, description. Pivots are `golem15_journal_posts_categories` and `golem15_journal_posts_tags`. +- **Settings.** Singleton row ID=1 on `golem15_journal_settings`, not PHP `system_settings`. `search_use_typesense` defaults to false. +- **Author slug.** Plugin migration adds `backend_users.golem15_bloghub_author_slug`. +- **Phrasebooks.** Embedded `lang/en/lang.yaml` and `lang/pl/lang.yaml` hold plugin, journal, post, category, and tag UI strings. They are independent of model translation JSON. +- **Schema.** Seven gormigrate steps create the six journal tables and the author-slug column. AutoMigrate is not used. Table names are `golem15_journal_*` only. + +## Usage + +Mount the plugin in an application as a git submodule at `plugins/golem15/journal`: + +```sh +git submodule add git@git.golem15.com:golem15/sm-journal-plugin.git plugins/golem15/journal +``` + +Add it to the application's `go.work`: + +```text +use ( + . + ./plugins/golem15/journal +) +``` + +Require it in the application's `go.mod` with a local replace: + +```text +require git.golem15.com/golem15/sm-journal-plugin v0.0.0 + +replace git.golem15.com/golem15/sm-journal-plugin => ./plugins/golem15/journal +``` + +List it in `summer.yaml` after translate and run `summer build`: + +```yaml +module: git.golem15.com/golem15/acme +binary: acme +plugins: + - id: golem15.translate + module: git.golem15.com/golem15/sm-translate-plugin + - id: golem15.journal + module: git.golem15.com/golem15/sm-journal-plugin +``` + +The plugin's own `go.mod` replaces the framework with `../summercms.go` for a sibling checkout next to `summercms.go`. A host application that mounts the plugin at `plugins/golem15/journal` should replace the framework from the host `go.work`. + +`Requires()` is exactly `golem15.translate`. + +A blog post model already declares translatable fields: + +```go +func (Post) Translatable() []string { + return []string{"title", "content", "content_html", "excerpt", "metadata", "slug"} +} +func (Post) TranslatableIndexes() []string { return []string{"slug"} } +func (Post) MorphName() string { return `Golem15\Journal\Models\Post` } +``` + +## API reference + +| Identifier | Description | +|------------|-------------| +| `journal.Plugin` | Compiled plugin; `ID` is `golem15.journal`. | +| `journal.Plugin.Requires` | Returns `golem15.translate` only. | +| `journal.Plugin.LangFS` | Embedded English and Polish UI phrasebooks. | +| `journal.Plugin.Migrations` | gormigrate steps registered by this plugin. | +| `journal.Plugin.Models` | Models registered by this plugin. | +| `models.Post` | Post row; `TableName` is `golem15_journal_posts`. | +| `models.Post.MorphName` | PHP class string `Golem15\Journal\Models\Post`. | +| `models.Post.Fillable` | Allow-list excluding `redactor_id`, `content_html`, and `user_id`. | +| `models.Post.AttachRelations` | `featured_images` and `content_images` attachMany. | +| `models.Category` | Category row; `TableName` is `golem15_journal_categories`. | +| `models.Category.MorphName` | PHP class string `Golem15\Journal\Models\Category`. | +| `models.Category.Fillable` | `name`, `slug`, `code`, `description`, `parent_id`. | +| `models.Tag` | Tag row; `TableName` is `golem15_journal_tags`. | +| `models.Tag.Fillable` | `name`, `slug`, `description`. | +| `models.Settings` | Singleton settings row; `TableName` is `golem15_journal_settings`. | +| `models.All` | Models registered by this plugin. | +| `updates.All` | gormigrate steps registered by this plugin. | + +## Dependencies + +- SummerCMS modules: backpack, cabana, lagoon/attach, pact, party. +- Compiled plugin: `golem15.translate`. +- Third-party: `gorm.io/gorm`, `github.com/go-gormigrate/gormigrate/v2`. Test-only: `github.com/testcontainers/testcontainers-go`. + +## Testing + +```sh +go test ./... +``` + +Postgres integration smokes start Docker unless `-short`. Missing Docker is a failure, not a skip, unless `-short`. diff --git a/lang/en/lang.yaml b/lang/en/lang.yaml new file mode 100644 index 0000000..795f276 --- /dev/null +++ b/lang/en/lang.yaml @@ -0,0 +1,122 @@ +plugin: + name: Journal + description: A robust journalging platform. +journal: + menu_label: Journal + menu_description: Manage Journal Posts + posts: Posts + create_post: Journal post + categories: Categories + create_category: Journal category + tab: Journal + access_posts: Manage the journal posts + access_categories: Manage the journal categories + access_other_posts: Manage other users journal posts + access_import_export: Allowed to import and export posts + access_publish: Allowed to publish posts + manage_settings: Manage journal settings + tags: Tags + create_tag: Journal tag + access_tags: Manage the journal tags + delete_confirm: Are you sure? + chart_published: Published + chart_drafts: Drafts + chart_total: Total + settings_description: Manage journal settings + show_all_posts_label: Show all posts to backend users + show_all_posts_comment: Display both published and unpublished posts on the frontend to backend users + tab_general: General + tab_search: Search + search_use_typesense_label: Use Typesense full-text search + search_use_typesense_comment: 'When enabled, the public posts API uses Laravel Scout + Typesense for relevance-ranked search (title weighted above content). When disabled, the standard database search is used. After enabling, run: php artisan scout:import "Golem15\Journal\Models\Post"' + search_title_weight_label: Title weight + search_title_weight_comment: Relevance weight for matches in the post title. Higher means title matches rank higher (default 5). + search_excerpt_weight_label: Excerpt weight + search_excerpt_weight_comment: Relevance weight for matches in the post excerpt (default 3). + search_content_weight_label: Content weight + search_content_weight_comment: Relevance weight for matches in the post body content (default 1). + use_rich_editor_label: Use the rich editor for journal content + use_rich_editor_comment: By default, journal posts are written using Markdown. If this is enabled, this changes the editor to an HTML based editor (WYSIWYG). + tab_rss: RSS Feed + rss_enabled_label: Enable RSS feed + rss_enabled_comment: When disabled, the RSS feed component will return a 404 response. + rss_include_content_label: Include full content + rss_include_content_comment: Include the full HTML content of each post in the feed alongside the summary. + rss_title_label: Feed title + rss_title_comment: Falls back to the page title if left empty. + rss_title_placeholder: Site name or feed title + rss_description_label: Feed description + rss_description_comment: Falls back to the page description if left empty. + rss_language_label: Feed language + rss_language_comment: Language code for the feed (e.g., en-us, de, fr). + rss_copyright_label: Copyright notice + rss_copyright_comment: Optional copyright notice displayed in the feed. + rss_image_url_label: Feed image URL + rss_image_url_comment: URL to an image/logo displayed in feed readers. + rss_posts_per_feed_label: Posts per feed + rss_posts_per_feed_comment: Default number of posts in the RSS feed. Can be overridden per component. +post: + label: Post + label_plural: Posts + title: Title + title_placeholder: New post title + content: Content + content_html: HTML Content + slug: Slug + slug_placeholder: new-post-slug + categories: Categories + author_email: Author Email + created: Created + created_date: Created date + updated: Updated + updated_date: Updated date + published: Published + published_by: Published by + preview_page: Preview Page + preview_page_comment: The page that should be used when previewing the post + current_user: Current user + published_date: Published date + published_validation: Please specify the published date + tab_edit: Edit + tab_categories: Categories + categories_comment: Select categories the journal post belongs to + categories_placeholder: There are no categories, you should create one first! + tags_comment: Select tags for the journal post + tags_placeholder: There are no tags, you should create one first! + tab_manage: Manage + published_on: Published on + excerpt: Excerpt + summary: Summary + featured_images: Featured Images + delete_confirm: Delete this post? + delete_success: Successfully deleted those posts. + close_confirm: The post is not saved. + return_to_posts: Return to posts list + posted_byline: 'Posted in :categories on :date.' + posted_byline_no_categories: 'Posted on :date.' + date_format: 'M d, Y' +category: + label: Category + label_plural: Categories + name: Name + name_placeholder: New category name + description: Description + slug: Slug + slug_placeholder: new-category-slug + posts: Posts + delete_confirm: Delete this category? + delete_success: Successfully deleted those categories. + return_to_categories: Return to the journal category list + reorder: Reorder Categories +tag: + label: Tag + label_plural: Tags + name: Name + name_placeholder: New tag name + description: Description + slug: Slug + slug_placeholder: new-tag-slug + posts: Posts + delete_confirm: Delete this tag? + delete_success: Successfully deleted those tags. + return_to_tags: Return to the journal tag list diff --git a/lang/pl/lang.yaml b/lang/pl/lang.yaml new file mode 100644 index 0000000..cd765be --- /dev/null +++ b/lang/pl/lang.yaml @@ -0,0 +1,74 @@ +plugin: + name: Journal + description: Solidna platforma journalera +journal: + menu_label: Journal + menu_description: Zarządzaj postami na journalu + posts: Posty + create_post: Utwórz post + categories: Kategorie + create_category: Utwórz kategorię + tab: Journal + access_posts: Zarządzaj postami + access_categories: Zarządzaj kategoriami na journalu + access_other_posts: Zarządzaj postami innych użytkowników + access_import_export: Zarządzaj importowaniem i eksportowaniem postów + access_publish: Publikuj posty + manage_settings: Zarządzaj ustawieniami journala + delete_confirm: Czy jesteś pewien? + chart_published: Opublikowane + chart_drafts: Szkice + chart_total: Łącznie + settings_description: Zarządzaj ustawieniami journala + show_all_posts_label: Pokaż wszystkie posty użytkownikom backendu + show_all_posts_comment: Wyświetl opublikowane i nieopublikowany posty na stronie dla użytkowników backendu + tab_general: Ogólne + tab_search: Wyszukiwanie + search_use_typesense_label: Użyj wyszukiwania pełnotekstowego Typesense + search_use_typesense_comment: 'Po włączeniu publiczne API postów korzysta z Laravel Scout + Typesense, aby sortować wyniki według trafności (tytuł ma większą wagę niż treść). Po wyłączeniu używane jest standardowe wyszukiwanie w bazie danych. Po włączeniu uruchom: php artisan scout:import "Golem15\Journal\Models\Post"' + search_title_weight_label: Waga tytułu + search_title_weight_comment: Waga trafności dla dopasowań w tytule posta. Wyższa wartość oznacza wyższą pozycję dopasowań w tytule (domyślnie 5). + search_excerpt_weight_label: Waga zajawki + search_excerpt_weight_comment: Waga trafności dla dopasowań w zajawce posta (domyślnie 3). + search_content_weight_label: Waga treści + search_content_weight_comment: Waga trafności dla dopasowań w treści posta (domyślnie 1). +post: + title: Tytuł + title_placeholder: Tytuł nowego posta + content: Zawartość + content_html: Zawartość HTML + slug: Alias + slug_placeholder: alias-nowego-postu + categories: Kategorie + author_email: Email autora + created: Utworzony + created_date: Data utworzenia + updated: Zaktualizowany + updated_date: Data aktualizacji + published: Opublikowany + published_date: Data publikacji + published_validation: Proszę określić datę publikacji + tab_edit: Edytuj + tab_categories: Kategorie + categories_comment: Wybierz kategorie do których post należy + categories_placeholder: Nie ma żadnej kategorii, powinieneś utworzyć przynajmniej jedną. + tab_manage: Zarządzaj + published_on: Opublikowane + excerpt: Zalążek + summary: Summary + featured_images: Załączone grafiki + delete_confirm: Czy naprawdę chcesz usunąć ten post? + delete_success: Posty zostały pomyślnie usunięte. + close_confirm: Ten post nie jest zapisany. + return_to_posts: Wróć do listy postów +category: + name: Nazwa + name_placeholder: Nazwa nowej kategorii + description: Opis + slug: Alias + slug_placeholder: alias-nowej-kategorii + posts: Posty + delete_confirm: Czy naprawdę chcesz usunąć tę kategorię? + delete_success: Kategorie zostały pomyślnie usunięte. + return_to_categories: Wróć do listy kategorii + reorder: Zmień kolejność kategorii diff --git a/plugin.go b/plugin.go index b11bd80..35b5a73 100644 --- a/plugin.go +++ b/plugin.go @@ -1,6 +1,9 @@ package journal import ( + "embed" + "io/fs" + "git.golem15.com/golem15/sm-journal-plugin/models" "git.golem15.com/golem15/sm-journal-plugin/updates" "git.golem15.com/golem15/summercms/modules/backpack" @@ -13,8 +16,12 @@ var ( _ party.Plugin = (*Plugin)(nil) _ pact.HasMigrations = (*Plugin)(nil) _ pact.HasModels = (*Plugin)(nil) + _ pact.HasLang = (*Plugin)(nil) ) +//go:embed lang +var langFS embed.FS + // Plugin is the golem15.journal plugin. type Plugin struct { app *backpack.App @@ -34,6 +41,8 @@ func (p *Plugin) Boot(app *backpack.App) error { return nil } +func (p *Plugin) LangFS() fs.FS { return langFS } + func (p *Plugin) Migrations() []*gormigrate.Migration { return updates.All() } func (p *Plugin) Models() []any { return models.All() }