astro-blog-kit 0.4.1 β†’ 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,10 +6,10 @@ A ready-to-use blog system for Astro with WordPress headless support, optional i
6
6
 
7
7
  - πŸš€ **One command setup** β€” `npx astro-blog-kit init`
8
8
  - πŸ“ **WordPress headless** β€” connects to WordPress REST API out of the box
9
- - 🎨 **Multiple layouts** β€” magazine, grid, and list
9
+ - 🎨 **Multiple layouts** β€” magazine, featured, grid, cards
10
10
  - πŸ’¬ **Comment system** β€” with secure proxy to WordPress
11
11
  - 🌍 **Optional i18n** β€” multi-language support
12
- - 🎨 **Themeable** β€” customize colors and fonts via `blogKit()` config
12
+ - 🎨 **Fully themeable** β€” colors, fonts, hero texts, and UI labels via `blog.config.ts`
13
13
  - πŸ“¦ **Zero config** β€” works without any configuration
14
14
 
15
15
  ---
@@ -47,20 +47,13 @@ And will automatically create:
47
47
 
48
48
  ```js
49
49
  // astro.config.mjs
50
- import { defineConfig } from "astro/config";
51
- import { blogKit } from "astro-blog-kit/integration";
50
+ import { defineConfig } from 'astro/config';
51
+ import { blogKit } from 'astro-blog-kit/integration';
52
+ import config from './blog.config';
53
+ import { toBlogKitConfig } from 'astro-blog-kit';
52
54
 
53
55
  export default defineConfig({
54
- integrations: [
55
- blogKit({
56
- postsPerPage: 5,
57
- defaultLayout: "magazine",
58
- theme: {
59
- accent: "#facc15",
60
- fontHeading: "Georgia, serif",
61
- },
62
- }),
63
- ],
56
+ integrations: [blogKit(toBlogKitConfig(config))],
64
57
  });
65
58
  ```
66
59
 
@@ -93,66 +86,157 @@ npm run dev
93
86
  ### `blog.config.ts`
94
87
 
95
88
  ```ts
96
- import { defineBlogConfig } from "astro-blog-kit";
89
+ import { defineBlogConfig } from 'astro-blog-kit';
97
90
 
98
91
  export default defineBlogConfig({
99
- wpUrl: "https://cms.yourdomain.com",
92
+ wpUrl: import.meta.env.WP_API_URL || 'https://cms.yourdomain.com',
100
93
  postsPerPage: 5,
101
- defaultLayout: "magazine",
102
- locale: "en",
94
+ defaultLayout: 'magazine', // 'magazine' | 'grid' | 'featured' | 'cards'
95
+ locale: 'en', // 'en' | 'es'
96
+
103
97
  theme: {
104
- accent: "#facc15",
105
- background: "#ffffff",
106
- text: "#0a0a0a",
107
- fontHeading: "Georgia, serif",
108
- fontBody: "system-ui, sans-serif",
98
+ accent: '#facc15',
99
+ background: '#ffffff',
100
+ surface: '#f8f8f8',
101
+ text: '#0a0a0a',
102
+ muted: '#6b7280',
103
+ mutedLight: '#9ca3af',
104
+ border: '#e5e7eb',
105
+ black: '#0a0a0a',
106
+ white: '#ffffff',
107
+ fontHeading: 'Georgia, serif',
108
+ fontBody: 'system-ui, sans-serif',
109
+ fontMono: 'monospace',
110
+ fontDisplay: 'Georgia, serif',
111
+ containerMax: '1200px',
112
+ },
113
+
114
+ hero: {
115
+ tagline: 'Our Blog',
116
+ titleLine1: 'Latest',
117
+ titleLine2: 'Articles',
118
+ description: 'Welcome to our blog.',
119
+ },
120
+
121
+ ui: {
122
+ readMoreLabel: 'Read more β†’',
123
+ btnPrev: 'Previous',
124
+ btnNext: 'Next',
125
+ commentButtonColor: 'var(--bk-accent)',
126
+ commentButtonTextColor: 'var(--bk-black)',
127
+ paginationStyle: 'minimal', // 'minimal' | 'numbered'
128
+ // paginationBtnBg: '#facc15',
129
+ // paginationBtnText: '#0a0a0a',
130
+ // paginationBtnHoverBg: '#0a0a0a',
131
+ // paginationBtnHoverText: '#ffffff',
132
+ // paginationActiveBg: '#facc15',
133
+ // paginationActiveText: '#0a0a0a',
109
134
  },
110
135
  });
111
136
  ```
112
137
 
113
- ### Theme options
114
-
115
- | Option | Type | Default | Description |
116
- |--------|------|---------|-------------|
117
- | `accent` | `string` | `#facc15` | Primary accent color |
118
- | `background` | `string` | `#ffffff` | Background color |
119
- | `surface` | `string` | `#f8f8f8` | Surface color (cards, sidebars) |
120
- | `text` | `string` | `#0a0a0a` | Primary text color |
121
- | `muted` | `string` | `#6b7280` | Secondary text color |
122
- | `border` | `string` | `#e5e7eb` | Border color |
123
- | `fontHeading` | `string` | `Georgia, serif` | Heading font |
124
- | `fontBody` | `string` | `system-ui, sans-serif` | Body font |
125
- | `fontMono` | `string` | `monospace` | Monospace font |
126
- | `containerMax` | `string` | `1200px` | Max container width |
138
+ ---
139
+
140
+ ### theme
141
+
142
+ | Property | Default | Description |
143
+ | :------------- | :---------------------- | :---------------------------------- |
144
+ | `accent` | `#facc15` | Primary accent color |
145
+ | `background` | `#ffffff` | Blog section background |
146
+ | `surface` | `#f8f8f8` | Cards and sidebar background |
147
+ | `text` | `#0a0a0a` | Primary text color |
148
+ | `muted` | `#6b7280` | Secondary text color |
149
+ | `mutedLight` | `#9ca3af` | Subtle text color |
150
+ | `border` | `#e5e7eb` | Border color |
151
+ | `black` | `#0a0a0a` | Badge and title highlight |
152
+ | `white` | `#ffffff` | Text on dark backgrounds |
153
+ | `fontHeading` | `Georgia, serif` | Heading font |
154
+ | `fontBody` | `system-ui, sans-serif` | Body font |
155
+ | `fontMono` | `monospace` | Monospace font |
156
+ | `fontDisplay` | `Georgia, serif` | Display/hero font |
157
+ | `containerMax` | `1200px` | Max width of the blog container |
127
158
 
128
159
  ---
129
160
 
130
- ## Layouts
161
+ ### hero
162
+
163
+ | Property | Default | Description |
164
+ | :------------ | :--------------------- | :------------------------------ |
165
+ | `tagline` | `Our Blog` | Badge text above the title |
166
+ | `titleLine1` | `Latest` | First line of the hero title |
167
+ | `titleLine2` | `Articles` | Second line (highlighted) |
168
+ | `description` | `Welcome to our blog.` | Paragraph below the title |
131
169
 
132
- ### Magazine (default)
133
- Featured post on the left + smaller posts on the right.
170
+ ---
134
171
 
135
- ### Grid
136
- 3-column card grid (responsive: 2 cols on tablet, 1 on mobile).
172
+ ### ui
173
+
174
+ | Property | Default | Description |
175
+ | :----------------------- | :----------------- | :--------------------------------- |
176
+ | `readMoreLabel` | `Read more β†’` | Read more button label |
177
+ | `btnPrev` | `Previous` | Previous page button label |
178
+ | `btnNext` | `Next` | Next page button label |
179
+ | `commentButtonColor` | `var(--bk-accent)` | Comment submit button background |
180
+ | `commentButtonTextColor` | `var(--bk-black)` | Comment submit button text color |
181
+ | `paginationStyle` | `minimal` | `minimal` or `numbered` |
182
+ | `paginationBtnBg` | `accent` | PREV/NEXT button background |
183
+ | `paginationBtnText` | `black` | PREV/NEXT button text color |
184
+ | `paginationBtnHoverBg` | `text` | PREV/NEXT button hover background |
185
+ | `paginationBtnHoverText` | `white` | PREV/NEXT button hover text color |
186
+ | `paginationActiveBg` | `accent` | Active page number background |
187
+ | `paginationActiveText` | `black` | Active page number text color |
137
188
 
138
- ### List
139
- Horizontal rows with image on the left.
189
+ ---
140
190
 
141
- ```astro
142
- <BlogList layout="grid" ... />
143
- <BlogList layout="list" ... />
144
- <BlogList layout="magazine" ... />
191
+ ## CSS Variables
192
+
193
+ All theme values are available globally with the `--bk-` prefix:
194
+
195
+ ```css
196
+ var(--bk-accent)
197
+ var(--bk-background)
198
+ var(--bk-surface)
199
+ var(--bk-text)
200
+ var(--bk-muted)
201
+ var(--bk-muted-light)
202
+ var(--bk-border)
203
+ var(--bk-black)
204
+ var(--bk-white)
205
+ var(--bk-font-heading)
206
+ var(--bk-font-body)
207
+ var(--bk-font-mono)
208
+ var(--bk-font-display)
209
+ var(--bk-container-max)
210
+ var(--bk-pagination-btn-bg)
211
+ var(--bk-pagination-btn-text)
212
+ var(--bk-pagination-btn-hover-bg)
213
+ var(--bk-pagination-btn-hover-text)
214
+ var(--bk-pagination-active-bg)
215
+ var(--bk-pagination-active-text)
216
+ var(--bk-comment-btn-bg)
217
+ var(--bk-comment-btn-text)
145
218
  ```
146
219
 
147
220
  ---
148
221
 
222
+ ## Layouts
223
+
224
+ | Layout | Description |
225
+ | :---------- | :------------------------------------ |
226
+ | `magazine` | Featured post + side grid |
227
+ | `grid` | 3-column card grid |
228
+ | `featured` | Large hero image + grid below |
229
+ | `cards` | Image background with text overlay |
230
+
231
+ ---
232
+
149
233
  ## Components
150
234
 
151
235
  ### `<BlogList>`
152
236
 
153
237
  ```astro
154
238
  ---
155
- import { BlogList } from "astro-blog-kit/components";
239
+ import { BlogList } from 'astro-blog-kit/components';
156
240
  ---
157
241
  <BlogList
158
242
  posts={posts}
@@ -161,7 +245,7 @@ import { BlogList } from "astro-blog-kit/components";
161
245
  basePath="/blog/page/"
162
246
  blogBase="/blog/"
163
247
  dateLocale="en-US"
164
- t={t}
248
+ t={bt}
165
249
  locale="en"
166
250
  layout="magazine"
167
251
  />
@@ -171,7 +255,7 @@ import { BlogList } from "astro-blog-kit/components";
171
255
 
172
256
  ```astro
173
257
  ---
174
- import { BlogPost } from "astro-blog-kit/components";
258
+ import { BlogPost } from 'astro-blog-kit/components';
175
259
  ---
176
260
  <BlogPost post={post} t={t} lang="en" />
177
261
  ```
@@ -180,7 +264,7 @@ import { BlogPost } from "astro-blog-kit/components";
180
264
 
181
265
  ```astro
182
266
  ---
183
- import { Comments } from "astro-blog-kit/components";
267
+ import { Comments } from 'astro-blog-kit/components';
184
268
  ---
185
269
  <Comments comments={comments} postId={post.id ?? 0} />
186
270
  ```
@@ -189,7 +273,7 @@ import { Comments } from "astro-blog-kit/components";
189
273
 
190
274
  ```astro
191
275
  ---
192
- import { CommentForm } from "astro-blog-kit/components";
276
+ import { CommentForm } from 'astro-blog-kit/components';
193
277
  ---
194
278
  <CommentForm postId={post.id ?? 0} apiRoute="/api/comments" />
195
279
  ```
@@ -201,30 +285,21 @@ import { CommentForm } from "astro-blog-kit/components";
201
285
  ### WordPress client
202
286
 
203
287
  ```ts
204
- import { createWPClient } from "astro-blog-kit/utils";
288
+ import { createWPClient } from 'astro-blog-kit/utils';
205
289
 
206
- const wp = createWPClient("https://cms.yourdomain.com");
290
+ const wp = createWPClient('https://cms.yourdomain.com');
207
291
 
208
- // Get paginated posts
209
292
  const { posts, total, totalPages } = await wp.getPosts({ perPage: 5, page: 1 });
210
-
211
- // Get all posts (for getStaticPaths)
212
293
  const posts = await wp.getAllPosts();
213
-
214
- // Get single post by slug
215
- const post = await wp.getPost("my-post-slug");
216
-
217
- // Get comments for a post
294
+ const post = await wp.getPost('my-post-slug');
218
295
  const comments = await wp.getComments(postId);
219
-
220
- // Get categories
221
296
  const categories = await wp.getCategories();
222
297
  ```
223
298
 
224
299
  ### i18n helpers
225
300
 
226
301
  ```ts
227
- import { getLang, useTranslations, getBlogBase } from "astro-blog-kit/utils";
302
+ import { getLang, useTranslations, getBlogBase } from 'astro-blog-kit/utils';
228
303
 
229
304
  const lang = getLang(Astro.url, import.meta.env.BASE_URL, config);
230
305
  const t = useTranslations(lang, { en, es });
@@ -234,12 +309,9 @@ const blogBase = getBlogBase(lang, import.meta.env.BASE_URL, config);
234
309
  ### Static paths helpers
235
310
 
236
311
  ```ts
237
- import { getStaticPathsForPosts, getStaticPathsForPages } from "astro-blog-kit/utils";
312
+ import { getStaticPathsForPosts, getStaticPathsForPages } from 'astro-blog-kit/utils';
238
313
 
239
- // For [...slug].astro
240
314
  export const getStaticPaths = () => getStaticPathsForPosts(posts);
241
-
242
- // For [page].astro
243
315
  export const getStaticPaths = () => getStaticPathsForPages(posts, { postsPerPage: 5 });
244
316
  ```
245
317
 
@@ -251,19 +323,12 @@ Copy `node_modules/astro-blog-kit/examples/deploy-ftp.yml` to `.github/workflows
251
323
 
252
324
  Add these secrets to your GitHub repository (**Settings β†’ Secrets**):
253
325
 
254
- | Secret | Description |
255
- |--------|-------------|
256
- | `FTP_HOST` | Your server hostname |
257
- | `FTP_USERNAME` | FTP username |
258
- | `FTP_PASSWORD` | FTP password |
259
- | `WP_API_URL` | Your WordPress URL |
260
-
261
- The workflow triggers on:
262
- - Push to `main`
263
- - WordPress webhook (`wp_update`, `wp_comment`)
264
- - Manual trigger
265
-
266
- To trigger from WordPress, install the **WP Webhooks** plugin and configure it to send a `repository_dispatch` event to GitHub when a post is published or a comment is approved.
326
+ | Secret | Description |
327
+ | :------------ | :-------------------- |
328
+ | `FTP_HOST` | Your server hostname |
329
+ | `FTP_USERNAME`| FTP username |
330
+ | `FTP_PASSWORD`| FTP password |
331
+ | `WP_API_URL` | Your WordPress URL |
267
332
 
268
333
  ---
269
334
 
@@ -273,8 +338,8 @@ To trigger from WordPress, install the **WP Webhooks** plugin and configure it t
273
338
  // astro.config.mjs
274
339
  blogKit({
275
340
  i18n: {
276
- locales: ["en", "es"],
277
- defaultLocale: "en",
341
+ locales: ['en', 'es'],
342
+ defaultLocale: 'en',
278
343
  },
279
344
  })
280
345
  ```
@@ -287,7 +352,7 @@ With i18n enabled:
287
352
 
288
353
  ## Requirements
289
354
 
290
- - Astro v4 or v5
355
+ - Astro v4, v5, v6, or v7
291
356
  - Node.js 18+
292
357
  - WordPress with REST API enabled
293
358
 
package/define-config.ts CHANGED
@@ -2,7 +2,16 @@
2
2
  // astro-blog-kit Β· define-config.ts
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
- import type { BlogKitConfig, BlogTheme, BlogHero, BlogUI } from "./types";
5
+ import type {
6
+ BlogKitConfig,
7
+ BlogTheme,
8
+ BlogHero,
9
+ BlogHeroLocale,
10
+ BlogUI,
11
+ BlogUILocale,
12
+ } from "./types";
13
+
14
+ // ── Interfaces pΓΊblicas ───────────────────────────────────────
6
15
 
7
16
  export interface BlogConfig {
8
17
  /** URL de tu WordPress. Ej: https://cms.tudominio.com */
@@ -26,132 +35,170 @@ export interface BlogConfig {
26
35
  };
27
36
  }
28
37
 
29
- /**
30
- * Define la configuraciΓ³n del blog con tipado completo.
31
- *
32
- * @example
33
- * ```ts
34
- * // blog.config.ts
35
- * import { defineBlogConfig } from 'astro-blog-kit';
36
- *
37
- * export default defineBlogConfig({
38
- * wpUrl: 'https://cms.tudominio.com',
39
- * postsPerPage: 5,
40
- * defaultLayout: 'featured',
41
- * locale: 'en',
42
- * theme: {
43
- * accent: '#facc15',
44
- * background: '#0a1a0a',
45
- * text: '#ffffff',
46
- * },
47
- * hero: {
48
- * tagline: 'Technical Resources',
49
- * titleLine1: 'Building',
50
- * titleLine2: 'Insights',
51
- * description: 'Practical knowledge for architects and engineers.',
52
- * },
53
- * ui: {
54
- * readMoreLabel: 'Read more β†’',
55
- * btnPrev: 'Previous',
56
- * btnNext: 'Next',
57
- * commentButtonColor: '#facc15',
58
- * commentButtonTextColor: '#0a0a0a',
59
- * paginationStyle: 'minimal',
60
- * },
61
- * });
62
- * ```
63
- */
38
+ // ── defineBlogConfig ──────────────────────────────────────────
39
+
64
40
  export function defineBlogConfig(config: BlogConfig): BlogConfig {
65
41
  return {
66
- postsPerPage: 5,
42
+ postsPerPage: 5,
67
43
  defaultLayout: "magazine",
68
- locale: "en",
44
+ locale: "en",
69
45
  ...config,
70
46
  };
71
47
  }
72
48
 
73
- /**
74
- * Convierte BlogConfig a BlogKitConfig para usar en astro.config.mjs
75
- */
49
+ // ── toBlogKitConfig ───────────────────────────────────────────
50
+
76
51
  export function toBlogKitConfig(config: BlogConfig): BlogKitConfig {
77
52
  return {
78
- postsPerPage: config.postsPerPage,
53
+ postsPerPage: config.postsPerPage,
79
54
  defaultLayout: config.defaultLayout,
80
- theme: config.theme,
81
- hero: config.hero,
82
- ui: config.ui,
83
- i18n: config.i18n,
55
+ theme: config.theme,
56
+ hero: config.hero,
57
+ ui: config.ui,
58
+ i18n: config.i18n,
84
59
  };
85
60
  }
86
61
 
87
- /**
88
- * Resuelve los textos del hero con fallbacks segΓΊn el locale.
89
- * Usado internamente por BlogList.astro
90
- */
62
+ // ── Helpers de detecciΓ³n de formato ──────────────────────────
63
+
64
+ function isHeroI18n(hero: BlogHero): hero is Record<string, BlogHeroLocale> {
65
+ return typeof hero === "object" &&
66
+ Object.values(hero).some((v) => typeof v === "object" && v !== null);
67
+ }
68
+
69
+ function isUIi18n(ui: BlogUI): ui is Record<string, BlogUILocale> {
70
+ return typeof ui === "object" &&
71
+ Object.values(ui).some((v) => typeof v === "object" && v !== null);
72
+ }
73
+
74
+ // ── Defaults internos ─────────────────────────────────────────
75
+
76
+ const HERO_DEFAULTS: Record<string, Required<BlogHeroLocale>> = {
77
+ en: {
78
+ tagline: "Our Blog",
79
+ titleLine1: "Latest",
80
+ titleLine2: "Articles",
81
+ description: "Welcome to our blog.",
82
+ },
83
+ es: {
84
+ tagline: "Nuestro Blog",
85
+ titleLine1: "Últimos",
86
+ titleLine2: "ArtΓ­culos",
87
+ description: "Bienvenido a nuestro blog.",
88
+ },
89
+ };
90
+
91
+ const UI_DEFAULTS: Record<string, Required<BlogUILocale>> = {
92
+ en: {
93
+ readMoreLabel: "Read more β†’",
94
+ btnPrev: "Previous",
95
+ btnNext: "Next",
96
+ commentButtonColor: "var(--bk-accent)",
97
+ commentButtonTextColor: "var(--bk-black)",
98
+ paginationStyle: "minimal",
99
+ paginationBtnBg: "var(--bk-accent)",
100
+ paginationBtnText: "var(--bk-black)",
101
+ paginationBtnHoverBg: "var(--bk-text)",
102
+ paginationBtnHoverText: "var(--bk-white)",
103
+ paginationActiveBg: "var(--bk-accent)",
104
+ paginationActiveText: "var(--bk-black)",
105
+ },
106
+ es: {
107
+ readMoreLabel: "Leer mΓ‘s β†’",
108
+ btnPrev: "Anterior",
109
+ btnNext: "Siguiente",
110
+ commentButtonColor: "var(--bk-accent)",
111
+ commentButtonTextColor: "var(--bk-black)",
112
+ paginationStyle: "minimal",
113
+ paginationBtnBg: "var(--bk-accent)",
114
+ paginationBtnText: "var(--bk-black)",
115
+ paginationBtnHoverBg: "var(--bk-text)",
116
+ paginationBtnHoverText: "var(--bk-white)",
117
+ paginationActiveBg: "var(--bk-accent)",
118
+ paginationActiveText: "var(--bk-black)",
119
+ },
120
+ };
121
+
122
+ // ── resolveHero ───────────────────────────────────────────────
123
+
91
124
  export function resolveHero(
92
125
  hero: BlogHero | undefined,
93
126
  locale: string
94
- ): Required<BlogHero> {
95
- const defaults: Record<string, Required<BlogHero>> = {
96
- en: {
97
- tagline: "Our Blog",
98
- titleLine1: "Latest",
99
- titleLine2: "Articles",
100
- description: "Welcome to our blog.",
101
- },
102
- es: {
103
- tagline: "Nuestro Blog",
104
- titleLine1: "Últimos",
105
- titleLine2: "ArtΓ­culos",
106
- description: "Bienvenido a nuestro blog.",
107
- },
108
- };
127
+ ): Required<BlogHeroLocale> {
128
+ const d = HERO_DEFAULTS[locale] ?? HERO_DEFAULTS["en"];
109
129
 
110
- const d = defaults[locale] ?? defaults["en"];
130
+ if (!hero) return d;
111
131
 
132
+ if (isHeroI18n(hero)) {
133
+ // Formato i18n: busca locale exacto β†’ fallback 'en' β†’ defaults internos
134
+ const src = hero[locale] ?? hero["en"] ?? {};
135
+ return {
136
+ tagline: src.tagline ?? d.tagline,
137
+ titleLine1: src.titleLine1 ?? d.titleLine1,
138
+ titleLine2: src.titleLine2 ?? d.titleLine2,
139
+ description: src.description ?? d.description,
140
+ };
141
+ }
142
+
143
+ // Formato plano legacy β€” backward compatible
144
+ const flat = hero as BlogHeroLocale;
112
145
  return {
113
- tagline: hero?.tagline ?? d.tagline,
114
- titleLine1: hero?.titleLine1 ?? d.titleLine1,
115
- titleLine2: hero?.titleLine2 ?? d.titleLine2,
116
- description: hero?.description ?? d.description,
146
+ tagline: flat.tagline ?? d.tagline,
147
+ titleLine1: flat.titleLine1 ?? d.titleLine1,
148
+ titleLine2: flat.titleLine2 ?? d.titleLine2,
149
+ description: flat.description ?? d.description,
117
150
  };
118
151
  }
119
152
 
120
- /**
121
- * Resuelve los labels de UI con fallbacks segΓΊn el locale.
122
- * Usado internamente por BlogList.astro y Pagination.astro
123
- */
153
+ // ── resolveUI ─────────────────────────────────────────────────
154
+
124
155
  export function resolveUI(
125
156
  ui: BlogUI | undefined,
126
157
  locale: string
127
- ): Required<BlogUI> {
128
- const defaults: Record<string, Required<BlogUI>> = {
129
- en: {
130
- readMoreLabel: "Read more β†’",
131
- btnPrev: "Previous",
132
- btnNext: "Next",
133
- commentButtonColor: "var(--bk-accent)",
134
- commentButtonTextColor: "var(--bk-black)",
135
- paginationStyle: "minimal",
136
- },
137
- es: {
138
- readMoreLabel: "Leer mΓ‘s β†’",
139
- btnPrev: "Anterior",
140
- btnNext: "Siguiente",
141
- commentButtonColor: "var(--bk-accent)",
142
- commentButtonTextColor: "var(--bk-black)",
143
- paginationStyle: "minimal",
144
- },
145
- };
158
+ ): Required<BlogUILocale> {
159
+ const d = UI_DEFAULTS[locale] ?? UI_DEFAULTS["en"];
160
+
161
+ if (!ui) return d;
162
+
163
+ if (isUIi18n(ui)) {
164
+ // Campos visuales (colores) viven en cualquier locale; los buscamos
165
+ // en todos los locales disponibles para usarlos como fallback compartido.
166
+ const allLocales = Object.values(ui as Record<string, BlogUILocale>);
167
+ const visual = allLocales.find((v) => v.paginationBtnBg) ?? {};
168
+
169
+ const src = (ui as Record<string, BlogUILocale>)[locale] ??
170
+ (ui as Record<string, BlogUILocale>)["en"] ?? {};
146
171
 
147
- const d = defaults[locale] ?? defaults["en"];
172
+ return {
173
+ readMoreLabel: src.readMoreLabel ?? d.readMoreLabel,
174
+ btnPrev: src.btnPrev ?? d.btnPrev,
175
+ btnNext: src.btnNext ?? d.btnNext,
176
+ commentButtonColor: src.commentButtonColor ?? visual.commentButtonColor ?? d.commentButtonColor,
177
+ commentButtonTextColor: src.commentButtonTextColor ?? visual.commentButtonTextColor ?? d.commentButtonTextColor,
178
+ paginationStyle: src.paginationStyle ?? visual.paginationStyle ?? d.paginationStyle,
179
+ paginationBtnBg: src.paginationBtnBg ?? visual.paginationBtnBg ?? d.paginationBtnBg,
180
+ paginationBtnText: src.paginationBtnText ?? visual.paginationBtnText ?? d.paginationBtnText,
181
+ paginationBtnHoverBg: src.paginationBtnHoverBg ?? visual.paginationBtnHoverBg ?? d.paginationBtnHoverBg,
182
+ paginationBtnHoverText: src.paginationBtnHoverText ?? visual.paginationBtnHoverText ?? d.paginationBtnHoverText,
183
+ paginationActiveBg: src.paginationActiveBg ?? visual.paginationActiveBg ?? d.paginationActiveBg,
184
+ paginationActiveText: src.paginationActiveText ?? visual.paginationActiveText ?? d.paginationActiveText,
185
+ };
186
+ }
148
187
 
188
+ // Formato plano legacy β€” backward compatible
189
+ const flat = ui as BlogUILocale;
149
190
  return {
150
- readMoreLabel: ui?.readMoreLabel ?? d.readMoreLabel,
151
- btnPrev: ui?.btnPrev ?? d.btnPrev,
152
- btnNext: ui?.btnNext ?? d.btnNext,
153
- commentButtonColor: ui?.commentButtonColor ?? d.commentButtonColor,
154
- commentButtonTextColor: ui?.commentButtonTextColor ?? d.commentButtonTextColor,
155
- paginationStyle: ui?.paginationStyle ?? d.paginationStyle,
191
+ readMoreLabel: flat.readMoreLabel ?? d.readMoreLabel,
192
+ btnPrev: flat.btnPrev ?? d.btnPrev,
193
+ btnNext: flat.btnNext ?? d.btnNext,
194
+ commentButtonColor: flat.commentButtonColor ?? d.commentButtonColor,
195
+ commentButtonTextColor: flat.commentButtonTextColor ?? d.commentButtonTextColor,
196
+ paginationStyle: flat.paginationStyle ?? d.paginationStyle,
197
+ paginationBtnBg: flat.paginationBtnBg ?? d.paginationBtnBg,
198
+ paginationBtnText: flat.paginationBtnText ?? d.paginationBtnText,
199
+ paginationBtnHoverBg: flat.paginationBtnHoverBg ?? d.paginationBtnHoverBg,
200
+ paginationBtnHoverText: flat.paginationBtnHoverText ?? d.paginationBtnHoverText,
201
+ paginationActiveBg: flat.paginationActiveBg ?? d.paginationActiveBg,
202
+ paginationActiveText: flat.paginationActiveText ?? d.paginationActiveText,
156
203
  };
157
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-blog-kit",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "A ready-to-use blog system for Astro with WordPress headless support, optional i18n, multiple layouts, and a comment system.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,9 +2,9 @@ import { defineBlogConfig } from 'astro-blog-kit';
2
2
 
3
3
  export default defineBlogConfig({
4
4
  wpUrl: import.meta.env.WP_API_URL || '__WP_URL__',
5
- postsPerPage: __POSTS_PER_PAGE__,
5
+ postsPerPage: __POSTS_PER_PAGE__,
6
6
  defaultLayout: '__DEFAULT_LAYOUT__',
7
- locale: '__LOCALE__',
7
+ locale: '__LOCALE__',
8
8
 
9
9
  theme: {
10
10
  accent: '#facc15',
@@ -23,28 +23,49 @@ export default defineBlogConfig({
23
23
  containerMax: '1200px',
24
24
  },
25
25
 
26
+ // ── Hero ──────────────────────────────────────────────────────
27
+ // Sitio i18n: un objeto por locale.
28
+ // Sitio monolingΓΌe: puedes usar el formato plano:
29
+ // hero: { tagline: 'Our Blog', titleLine1: 'Latest', ... }
26
30
  hero: {
27
- tagline: '__T_TAGLINE__',
28
- titleLine1: '__T_TITLE_LINE1__',
29
- titleLine2: '__T_TITLE_LINE2__',
30
- description: '__T_DESCRIPTION__',
31
+ en: {
32
+ tagline: 'Our Blog',
33
+ titleLine1: 'Latest',
34
+ titleLine2: 'Articles',
35
+ description: 'Welcome to our blog.',
36
+ },
37
+ es: {
38
+ tagline: 'Nuestro Blog',
39
+ titleLine1: 'Últimos',
40
+ titleLine2: 'ArtΓ­culos',
41
+ description: 'Bienvenido a nuestro blog.',
42
+ },
31
43
  },
32
44
 
45
+ // ── UI labels ────────────────────────────────────────────────
46
+ // Los labels de texto van por locale.
47
+ // Los campos visuales (colores, paginationStyle) son compartidos:
48
+ // ponlos en cualquier locale y aplican a todos.
33
49
  ui: {
34
- readMoreLabel: '__T_BTNCTA__',
35
- btnPrev: '__T_BTN_PREV__',
36
- btnNext: '__T_BTN_NEXT__',
37
- commentButtonColor: 'var(--bk-accent)',
38
- commentButtonTextColor: 'var(--bk-black)',
39
- paginationStyle: 'minimal',
40
- // ── PersonalizaciΓ³n de paginaciΓ³n (opcional) ──────────────
41
- // Descomenta y cambia los valores segΓΊn tu marca.
42
- // Por defecto usan el accent y black del tema.
43
- // paginationBtnBg: '#facc15', // fondo botΓ³n PREV/NEXT
44
- // paginationBtnText: '#0a0a0a', // texto botΓ³n PREV/NEXT
45
- // paginationBtnHoverBg: '#0a0a0a', // fondo hover
46
- // paginationBtnHoverText: '#ffffff', // texto hover
47
- // paginationActiveBg: '#facc15', // fondo pΓ‘gina activa
48
- // paginationActiveText: '#0a0a0a', // texto pΓ‘gina activa
50
+ en: {
51
+ readMoreLabel: 'Read more β†’',
52
+ btnPrev: 'Previous',
53
+ btnNext: 'Next',
54
+ // ── Colores compartidos (van aquΓ­ una sola vez) ──────────
55
+ commentButtonColor: 'var(--bk-accent)',
56
+ commentButtonTextColor: 'var(--bk-black)',
57
+ paginationStyle: 'minimal',
58
+ // paginationBtnBg: '#facc15',
59
+ // paginationBtnText: '#0a0a0a',
60
+ // paginationBtnHoverBg: '#0a0a0a',
61
+ // paginationBtnHoverText: '#ffffff',
62
+ // paginationActiveBg: '#facc15',
63
+ // paginationActiveText: '#0a0a0a',
64
+ },
65
+ es: {
66
+ readMoreLabel: 'Leer mΓ‘s β†’',
67
+ btnPrev: 'Anterior',
68
+ btnNext: 'Siguiente',
69
+ },
49
70
  },
50
- });
71
+ });
package/types.ts CHANGED
@@ -29,109 +29,144 @@ export interface I18nConfig {
29
29
 
30
30
  export interface BlogTranslations {
31
31
  blog: {
32
- tagline: string;
32
+ tagline: string;
33
33
  title_line1: string;
34
34
  title_line2: string;
35
35
  description: string;
36
- btncta: string;
37
- btn_prev: string;
38
- btn_next: string;
36
+ btncta: string;
37
+ btn_prev: string;
38
+ btn_next: string;
39
39
  };
40
40
  }
41
41
 
42
42
  export interface PaginationProps {
43
43
  currentPage: number;
44
- totalPages: number;
45
- basePath: string;
46
- blogBase: string;
47
- t: BlogTranslations;
44
+ totalPages: number;
45
+ basePath: string;
46
+ blogBase: string;
47
+ t: BlogTranslations;
48
48
  }
49
49
 
50
50
  export interface BlogListProps {
51
- posts: BlogPost[];
51
+ posts: BlogPost[];
52
52
  currentPage: number;
53
- totalPages: number;
54
- basePath: string;
55
- blogBase: string;
56
- dateLocale: string;
57
- t: BlogTranslations;
58
- locale: string;
59
- layout?: BlogListLayout;
53
+ totalPages: number;
54
+ basePath: string;
55
+ blogBase: string;
56
+ dateLocale: string;
57
+ t: BlogTranslations;
58
+ locale: string;
59
+ layout?: BlogListLayout;
60
60
  }
61
61
 
62
62
  export interface BlogPostProps {
63
63
  post: BlogPost;
64
- t: BlogTranslations;
64
+ t: BlogTranslations;
65
65
  lang: string;
66
66
  }
67
67
 
68
+ // ── Theme ─────────────────────────────────────────────────────
69
+
68
70
  export interface BlogTheme {
69
- accent?: string;
70
- background?: string;
71
- surface?: string;
72
- text?: string;
73
- muted?: string;
74
- mutedLight?: string;
75
- border?: string;
76
- black?: string;
77
- white?: string;
78
- fontHeading?: string;
79
- fontBody?: string;
80
- fontMono?: string;
81
- fontDisplay?: string;
71
+ accent?: string;
72
+ background?: string;
73
+ surface?: string;
74
+ text?: string;
75
+ muted?: string;
76
+ mutedLight?: string;
77
+ border?: string;
78
+ black?: string;
79
+ white?: string;
80
+ fontHeading?: string;
81
+ fontBody?: string;
82
+ fontMono?: string;
83
+ fontDisplay?: string;
82
84
  containerMax?: string;
83
85
  }
84
86
 
85
- export interface BlogHero {
86
- tagline?: string;
87
- titleLine1?: string;
88
- titleLine2?: string;
87
+ // ── Hero ──────────────────────────────────────────────────────
88
+
89
+ /** Textos del hero para un ΓΊnico locale */
90
+ export interface BlogHeroLocale {
91
+ tagline?: string;
92
+ titleLine1?: string;
93
+ titleLine2?: string;
89
94
  description?: string;
90
95
  }
91
96
 
92
- export interface BlogUI {
97
+ /**
98
+ * Acepta dos formatos:
99
+ * - Plano (sitio monolingΓΌe): { tagline: 'Our Blog', ... }
100
+ * - Por locale (sitio i18n): { en: { tagline: 'Our Blog' }, es: { tagline: 'Nuestro Blog' } }
101
+ */
102
+ export type BlogHero = BlogHeroLocale | Record<string, BlogHeroLocale>;
103
+
104
+ // ── UI ────────────────────────────────────────────────────────
105
+
106
+ /** Labels y colores de UI para un ΓΊnico locale */
107
+ export interface BlogUILocale {
93
108
  /** Texto del botΓ³n "leer mΓ‘s". @default "Read more β†’" */
94
- readMoreLabel?: string;
109
+ readMoreLabel?: string;
95
110
  /** Texto del botΓ³n de pΓ‘gina anterior. @default "Previous" */
96
- btnPrev?: string;
111
+ btnPrev?: string;
97
112
  /** Texto del botΓ³n de pΓ‘gina siguiente. @default "Next" */
98
- btnNext?: string;
113
+ btnNext?: string;
99
114
  /** Color de fondo del botΓ³n de comentarios. @default var(--bk-accent) */
100
- commentButtonColor?: string;
115
+ commentButtonColor?: string;
101
116
  /** Color del texto del botΓ³n de comentarios. @default var(--bk-black) */
102
117
  commentButtonTextColor?: string;
103
118
  /** Estilo de paginaciΓ³n. @default "minimal" */
104
- paginationStyle?: "minimal" | "numbered";
105
- /** Fondo botones PREV/NEXT. @default accent */
106
- paginationBtnBg?: string;
107
- /** Texto botones PREV/NEXT. @default black */
108
- paginationBtnText?: string;
109
- /** Fondo botones PREV/NEXT en hover. @default text */
110
- paginationBtnHoverBg?: string;
111
- /** Texto botones PREV/NEXT en hover. @default white */
119
+ paginationStyle?: "minimal" | "numbered";
120
+ /** Fondo botones PREV/NEXT. @default var(--bk-accent) */
121
+ paginationBtnBg?: string;
122
+ /** Texto botones PREV/NEXT. @default var(--bk-black) */
123
+ paginationBtnText?: string;
124
+ /** Fondo botones PREV/NEXT en hover. @default var(--bk-text) */
125
+ paginationBtnHoverBg?: string;
126
+ /** Texto botones PREV/NEXT en hover. @default var(--bk-white) */
112
127
  paginationBtnHoverText?: string;
113
- /** Fondo pΓ‘gina activa. @default accent */
114
- paginationActiveBg?: string;
115
- /** Texto pΓ‘gina activa. @default black */
116
- paginationActiveText?: string;
128
+ /** Fondo pΓ‘gina activa. @default var(--bk-accent) */
129
+ paginationActiveBg?: string;
130
+ /** Texto pΓ‘gina activa. @default var(--bk-black) */
131
+ paginationActiveText?: string;
117
132
  }
118
133
 
134
+ /**
135
+ * Acepta dos formatos:
136
+ * - Plano (sitio monolingΓΌe): { readMoreLabel: 'Read more', ... }
137
+ * - Por locale (sitio i18n): { en: { readMoreLabel: 'Read more' }, es: { readMoreLabel: 'Leer mΓ‘s' } }
138
+ *
139
+ * Nota: los campos visuales (colores, paginationStyle) son compartidos entre locales.
140
+ * Ponlos en cualquier locale (ej. `en`) β€” el resolver los tomarΓ‘ como fallback global.
141
+ */
142
+ export type BlogUI = BlogUILocale | Record<string, BlogUILocale>;
143
+
144
+ // ── Alias para backward compat ────────────────────────────────
145
+ /** @deprecated Usa BlogHeroLocale */
146
+ export type { BlogHeroLocale as BlogHeroFlat };
147
+ /** @deprecated Usa BlogUILocale */
148
+ export type { BlogUILocale as BlogUIFlat };
149
+
150
+ // ── BlogKitConfig (integration.ts) ───────────────────────────
151
+
119
152
  export interface BlogKitConfig {
120
- postsPerPage?: number;
121
- i18n?: I18nConfig;
122
- defaultLayout?: BlogListLayout;
153
+ postsPerPage?: number;
154
+ i18n?: I18nConfig;
155
+ defaultLayout?: BlogListLayout;
123
156
  collectionName?: string;
124
- theme?: BlogTheme;
125
- hero?: BlogHero;
126
- ui?: BlogUI;
157
+ theme?: BlogTheme;
158
+ hero?: BlogHero;
159
+ ui?: BlogUI;
127
160
  }
128
161
 
162
+ // ── Static paths ──────────────────────────────────────────────
163
+
129
164
  export interface PageStaticPath {
130
165
  params: { page: string };
131
- props: { posts: BlogPost[]; currentPage: number; totalPages: number };
166
+ props: { posts: BlogPost[]; currentPage: number; totalPages: number };
132
167
  }
133
168
 
134
169
  export interface PostStaticPath {
135
170
  params: { slug: string };
136
- props: { post: BlogPost };
171
+ props: { post: BlogPost };
137
172
  }