From e14a4250efb828262347f5a4bbc4067cd71d885f Mon Sep 17 00:00:00 2001 From: grm Date: Mon, 14 Sep 2026 01:32:25 +0300 Subject: Add languages: English and Greek, chosen per blog on the Settings tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every blog and its dashboard were hard-wired to English. A blogger can now pick the language of their blog; it switches the whole dashboard and the blog's fixed text — post dates, archive months, the RSS link, the pager, the 404 page — while what the blogger wrote is left alone. The new internal/i18n package keys translations by the English string, so an untranslated key renders as English rather than blank, and TestGreekCatalogComplete scans the templates and handlers to fail when the Greek catalog misses a key or keeps a stale one. Templates are compiled once per language with t/tf/date/postdate/month closed over the language, so they need no data plumbing. The language lives in settings.language (blog migration 00002), not in the theme, so "Reset design" does not touch it. Public pages use the blog's language; management pages use the logged-in user's own blog's, so a superadmin editing someone else's blog keeps theirs; the login page follows Accept-Language. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- AGENTS.md | 33 +- README.md | 3 + cmd/blogspace/seed.go | 2 +- internal/db/migrations/blog/00002_language.sql | 7 + internal/i18n/el.go | 429 +++++++++++++++++++++ internal/i18n/el_months.go | 7 + internal/i18n/i18n.go | 110 ++++++ internal/i18n/i18n_test.go | 63 +++ internal/store/blogs.go | 7 +- internal/web/handlers_admin.go | 28 +- internal/web/handlers_auth.go | 12 +- internal/web/handlers_dashboard.go | 15 +- internal/web/handlers_design.go | 37 +- internal/web/handlers_layout.go | 54 +-- internal/web/handlers_pages.go | 18 +- internal/web/handlers_posts.go | 18 +- internal/web/handlers_sections.go | 20 +- internal/web/layout.go | 20 +- internal/web/server.go | 72 +++- internal/web/templates.go | 50 ++- internal/web/templates/admin/delete_user.html | 10 +- internal/web/templates/admin/index.html | 28 +- internal/web/templates/admin/new_user.html | 14 +- internal/web/templates/auth/login.html | 10 +- internal/web/templates/blog/404.html | 6 +- internal/web/templates/blog/page.html | 10 +- internal/web/templates/blog/post.html | 2 +- internal/web/templates/dashboard/confirm.html | 14 +- internal/web/templates/dashboard/design.html | 178 ++++----- internal/web/templates/dashboard/images.html | 14 +- internal/web/templates/dashboard/layout.html | 102 ++--- internal/web/templates/dashboard/module_form.html | 28 +- internal/web/templates/dashboard/overview.html | 24 +- internal/web/templates/dashboard/page_form.html | 18 +- internal/web/templates/dashboard/pages.html | 20 +- internal/web/templates/dashboard/password.html | 12 +- internal/web/templates/dashboard/post_form.html | 20 +- internal/web/templates/dashboard/posts.html | 20 +- internal/web/templates/dashboard/section_form.html | 40 +- internal/web/templates/dashboard/sections.html | 26 +- internal/web/templates/dashboard/settings.html | 15 +- internal/web/templates/layouts/blog.html | 8 +- internal/web/templates/layouts/dashboard.html | 2 +- internal/web/templates/partials/dashnav.html | 26 +- internal/web/templates/partials/editor.html | 14 +- internal/web/templates/partials/imagepick.html | 4 +- internal/web/templates/partials/mdhelp.html | 10 +- internal/web/theme.go | 14 +- internal/web/web_test.go | 111 +++++- 49 files changed, 1302 insertions(+), 503 deletions(-) create mode 100644 internal/db/migrations/blog/00002_language.sql create mode 100644 internal/i18n/el.go create mode 100644 internal/i18n/el_months.go create mode 100644 internal/i18n/i18n.go create mode 100644 internal/i18n/i18n_test.go diff --git a/AGENTS.md b/AGENTS.md index 4ff23f8..97798dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,6 +52,8 @@ internal/store/ Store = control DB (users, blog registry, create/delete bl BlogStore = one blog's DB (settings, pages, posts, images, sections, modules, menu) internal/auth/ bcrypt, JWT issue/parse, cookie, HMAC CSRF internal/markdown/ Render(md) → sanitized HTML +internal/i18n/ languages, T/Tf (English keys → catalog), FormatDate/Month, Accept-Language Match; + el.go is the Greek catalog, el_months.go the month tables internal/slug/ Make/Valid/WithSuffix (Greek transliteration included) internal/web/ server.go (host router, middleware, render helpers) routes.go (all routes), handlers_*.go (sections = announcements, layout = modules + menu) @@ -65,7 +67,7 @@ internal/web/ server.go (host router, middleware, render helpers) - **One database per blog**: the registry row (`blogs`: id, owner, subdomain, `db_name`) lives in the control DB; everything else — a one-row `settings` - table (title, tagline, theme) plus pages, posts, images, sections, modules, + table (title, tagline, language, theme) plus pages, posts, images, sections, modules, menu_items — lives in `blog_` (`db.DBName`: dashes → underscores, so subdomains are capped at 58 chars). No table in a blog DB carries a blog id; the database is the scope. `store.Store` (control) hands out a @@ -189,6 +191,33 @@ internal/web/ server.go (host router, middleware, render helpers) `notice notice-