@vdaluz/astro-blog 0.10.0 → 0.12.0
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 +11 -16
- package/package.json +1 -1
- package/src/components/BlogPostMeta.astro +11 -2
- package/src/index.ts +2 -2
- package/src/lib/i18n.ts +13 -7
- package/src/lib/schema.ts +12 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @vdaluz/astro-blog
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@vdaluz/astro-blog)
|
|
3
4
|
[](https://github.com/vdaluz/astro-blog/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
7
|
A blog needs a listing page, pagination, related posts, tag filters, JSON-LD, RSS, and a Shiki-highlighted code theme - most of it undifferentiated work you rebuild every time you spin up an Astro site. `@vdaluz/astro-blog` packages that layer as token-driven components, so styling comes from your own CSS custom properties, not a hardcoded palette. Ships raw `.astro` and `.ts` - the consuming app's Astro/Vite compiles them (no prebuild step). Built for and proven in production across two sites, [vdaluz.com](https://vdaluz.com) and [imperfectsystems.com](https://imperfectsystems.com) - see [Consumers](#consumers).
|
|
6
8
|
|
|
@@ -12,21 +14,6 @@ A blog needs a listing page, pagination, related posts, tag filters, JSON-LD, RS
|
|
|
12
14
|
npm install @vdaluz/astro-blog
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
Alternatively, a pinned https tarball from a tag works too, with no registry involved:
|
|
16
|
-
|
|
17
|
-
```jsonc
|
|
18
|
-
// package.json
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@vdaluz/astro-blog": "https://github.com/vdaluz/astro-blog/archive/refs/tags/v0.9.0.tar.gz"
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
> **Why a tarball, not `github:vdaluz/astro-blog#v0.1.0`?** npm canonicalizes GitHub
|
|
25
|
-
> shorthand (and even an explicit `git+https://` URL) to `git+ssh://` in the lockfile.
|
|
26
|
-
> CI runners (e.g. Cloudflare Pages/Workers) have no SSH key, so `npm ci` would fail to
|
|
27
|
-
> clone it. The `/archive/refs/tags/<tag>.tar.gz` URL is anonymous https with an integrity
|
|
28
|
-
> hash in the lockfile - it just works in CI. Bump the tag in the URL to upgrade.
|
|
29
|
-
|
|
30
17
|
Peer dependency: `astro` >= 6. For post body styling you'll also want `@tailwindcss/typography` in the app.
|
|
31
18
|
|
|
32
19
|
## Four things every consumer MUST do
|
|
@@ -97,7 +84,7 @@ Peer dependency: `astro` >= 6. For post body styling you'll also want `@tailwind
|
|
|
97
84
|
|
|
98
85
|
Components that build post URLs (`PostCard`, `RelatedPosts`, `Pagination`) accept an optional `base` prop (default `/blog`).
|
|
99
86
|
|
|
100
|
-
`PostCard`, `RelatedPosts`, `Pagination`, and `BlogPostMeta` accept an optional `locale` prop (
|
|
87
|
+
`PostCard`, `RelatedPosts`, `Pagination`, and `BlogPostMeta` accept an optional `locale` prop (default `'en'`) that localizes their built-in UI strings (dates, "Read More", pagination labels) and `BlogPostMeta`'s JSON-LD `inLanguage` field. `Locale` ships built-in strings for `'en' | 'es' | 'pt'` (autocompleted in editors) but accepts any string - an unrecognized locale falls back to the `en` strings and passes the raw string through to `Intl.DateTimeFormat` for date formatting. Call `t(locale, overrides)` directly with a `Partial<Strings>` to supply your own strings for a locale the package doesn't ship. It does not affect the post URLs those components build - a locale-specific `base` still needs passing separately if the consuming app routes translated posts under a different prefix (e.g. `/es/blog`).
|
|
101
88
|
|
|
102
89
|
`PostCard` accepts an optional `categoryLabel` prop to override the category badge text (default `post.data.category`). `RelatedPosts` accepts the same override as a `(post) => string` function, since it renders a badge per post. Use these when `category` is a canonical/English taxonomy value that the consuming app translates for display - the package has no built-in category translation since the taxonomy itself is app-defined.
|
|
103
90
|
|
|
@@ -119,6 +106,10 @@ import HeroImageCredit from '@vdaluz/astro-blog/HeroImageCredit.astro';
|
|
|
119
106
|
|
|
120
107
|
Set `updatedDate` in a post's frontmatter when you substantively edit it after publishing. `buildBlogPostingSchema` uses it for the JSON-LD `dateModified` field, falling back to `pubDate` when unset - so an edited post can signal freshness without every post needing the field.
|
|
121
108
|
|
|
109
|
+
### Trailing slash
|
|
110
|
+
|
|
111
|
+
`buildBlogPostingSchema`/`BlogPostMeta` build the JSON-LD `url`/`mainEntityOfPage.@id` fields without a trailing slash by default. If your site's actual canonical post URL is slash-terminated, pass `trailingSlash={true}` - otherwise the JSON-LD `url` disagrees with your page's own `<link rel="canonical">`, which can cause search engines to pick the wrong canonical form. Check your real canonical output before setting this, not just your app's `trailingSlash` config: prerendered routes on some hosts are served slash-terminated regardless of that config (confirm with `curl -sI` on a bare post URL - a `307`/`308` to the slash form means you need `trailingSlash={true}`).
|
|
112
|
+
|
|
122
113
|
### Table of contents + reading time
|
|
123
114
|
|
|
124
115
|
`TableOfContents` reads the `headings` array Astro's own `render()` already returns - no separate parsing step. It renders nothing if the post has fewer than `minHeadings` (default 3) h2/h3 headings.
|
|
@@ -236,3 +227,7 @@ Issues welcome. PRs by discussion - open an issue first for anything beyond a ty
|
|
|
236
227
|
|
|
237
228
|
- [vdaluz.com](https://vdaluz.com)
|
|
238
229
|
- [imperfectsystems.com](https://imperfectsystems.com)
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdaluz/astro-blog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Token-driven Astro blog components, related-posts scoring, a schema factory, and Shiki config - proven in production on vdaluz.com and imperfectsystems.com.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -13,10 +13,19 @@ interface Props {
|
|
|
13
13
|
publisherName?: string;
|
|
14
14
|
/** Locale for the JSON-LD `inLanguage` field. Omitted if unset. */
|
|
15
15
|
locale?: Locale;
|
|
16
|
+
/** Whether this site's canonical post URL ends in a trailing slash. Defaults to false. */
|
|
17
|
+
trailingSlash?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
const { post, siteUrl, basePath = '/blog', publisherName, locale } = Astro.props;
|
|
19
|
-
const schema = buildBlogPostingSchema({
|
|
20
|
+
const { post, siteUrl, basePath = '/blog', publisherName, locale, trailingSlash } = Astro.props;
|
|
21
|
+
const schema = buildBlogPostingSchema({
|
|
22
|
+
post,
|
|
23
|
+
siteUrl,
|
|
24
|
+
basePath,
|
|
25
|
+
publisherName,
|
|
26
|
+
locale,
|
|
27
|
+
trailingSlash,
|
|
28
|
+
});
|
|
20
29
|
---
|
|
21
30
|
|
|
22
31
|
{/* JSON-LD is valid in <body>; render this anywhere inside the post page. */}
|
package/src/index.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { shikiConfig } from './lib/shiki';
|
|
|
7
7
|
export { buildRssItems } from './lib/rss';
|
|
8
8
|
export type { RssItem } from './lib/rss';
|
|
9
9
|
export type { BlogPostData, BlogPostLike, HeroImageCredit } from './lib/types';
|
|
10
|
-
export { t, formatDate } from './lib/i18n';
|
|
11
|
-
export type { Locale } from './lib/i18n';
|
|
10
|
+
export { t, formatDate, BUILT_IN_LOCALES } from './lib/i18n';
|
|
11
|
+
export type { Locale, Strings } from './lib/i18n';
|
package/src/lib/i18n.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const BUILT_IN_LOCALES = ['en', 'es', 'pt'] as const;
|
|
2
|
+
type BuiltInLocale = (typeof BUILT_IN_LOCALES)[number];
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/** Any string is accepted; 'en' | 'es' | 'pt' still autocomplete since they're valid members. */
|
|
5
|
+
export type Locale = BuiltInLocale | (string & {});
|
|
6
|
+
|
|
7
|
+
export interface Strings {
|
|
4
8
|
readMore: string;
|
|
5
9
|
read: string;
|
|
6
10
|
relatedReading: string;
|
|
@@ -17,7 +21,7 @@ interface Strings {
|
|
|
17
21
|
via: string;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
const STRINGS: Record<
|
|
24
|
+
const STRINGS: Record<BuiltInLocale, Strings> = {
|
|
21
25
|
en: {
|
|
22
26
|
readMore: 'Read More',
|
|
23
27
|
read: 'Read',
|
|
@@ -68,16 +72,18 @@ const STRINGS: Record<Locale, Strings> = {
|
|
|
68
72
|
},
|
|
69
73
|
};
|
|
70
74
|
|
|
71
|
-
export function t(locale: Locale = 'en'): Strings {
|
|
72
|
-
|
|
75
|
+
export function t(locale: Locale = 'en', overrides?: Partial<Strings>): Strings {
|
|
76
|
+
const base = STRINGS[locale as BuiltInLocale] ?? STRINGS.en;
|
|
77
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
const DATE_LOCALE: Record<
|
|
80
|
+
const DATE_LOCALE: Record<BuiltInLocale, string> = {
|
|
76
81
|
en: 'en-US',
|
|
77
82
|
es: 'es',
|
|
78
83
|
pt: 'pt-BR',
|
|
79
84
|
};
|
|
80
85
|
|
|
81
86
|
export function formatDate(date: Date, locale: Locale = 'en', options?: Intl.DateTimeFormatOptions): string {
|
|
82
|
-
|
|
87
|
+
const dateLocale = DATE_LOCALE[locale as BuiltInLocale] ?? locale;
|
|
88
|
+
return date.toLocaleDateString(dateLocale, options ?? { year: 'numeric', month: 'long', day: 'numeric' });
|
|
83
89
|
}
|
package/src/lib/schema.ts
CHANGED
|
@@ -49,6 +49,16 @@ export interface BlogPostingSchemaOptions {
|
|
|
49
49
|
publisherName?: string;
|
|
50
50
|
/** BCP 47 language tag for the `inLanguage` field, e.g. "en" or "es". Omitted if unset. */
|
|
51
51
|
locale?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether this site's actual served/canonical post URL ends in a trailing slash.
|
|
54
|
+
* Defaults to false. Check your own `<link rel="canonical">` output (and served-URL
|
|
55
|
+
* behavior - prerendered routes on some hosts are slash-terminated regardless of the
|
|
56
|
+
* app's own trailingSlash config) before setting this; don't assume it from the app
|
|
57
|
+
* config alone. Mismatching this from the real canonical produces a self-inconsistent
|
|
58
|
+
* page (JSON-LD `url` disagreeing with the declared canonical), which can cause search
|
|
59
|
+
* engines to pick the wrong canonical form.
|
|
60
|
+
*/
|
|
61
|
+
trailingSlash?: boolean;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
/**
|
|
@@ -61,10 +71,11 @@ export function buildBlogPostingSchema({
|
|
|
61
71
|
basePath = '/blog',
|
|
62
72
|
publisherName,
|
|
63
73
|
locale,
|
|
74
|
+
trailingSlash = false,
|
|
64
75
|
}: BlogPostingSchemaOptions) {
|
|
65
76
|
const origin = siteUrl.replace(/\/$/, '');
|
|
66
77
|
const prefix = basePath.replace(/\/$/, '');
|
|
67
|
-
const postUrl = `${origin}${prefix}/${post.id}`;
|
|
78
|
+
const postUrl = `${origin}${prefix}/${post.id}${trailingSlash ? '/' : ''}`;
|
|
68
79
|
const authorName = post.data.author || publisherName || '';
|
|
69
80
|
return {
|
|
70
81
|
'@context': 'https://schema.org',
|