@vdaluz/astro-blog 0.10.0 → 0.11.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 +5 -1
- 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
|
@@ -97,7 +97,7 @@ Peer dependency: `astro` >= 6. For post body styling you'll also want `@tailwind
|
|
|
97
97
|
|
|
98
98
|
Components that build post URLs (`PostCard`, `RelatedPosts`, `Pagination`) accept an optional `base` prop (default `/blog`).
|
|
99
99
|
|
|
100
|
-
`PostCard`, `RelatedPosts`, `Pagination`, and `BlogPostMeta` accept an optional `locale` prop (
|
|
100
|
+
`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
101
|
|
|
102
102
|
`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
103
|
|
|
@@ -119,6 +119,10 @@ import HeroImageCredit from '@vdaluz/astro-blog/HeroImageCredit.astro';
|
|
|
119
119
|
|
|
120
120
|
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
121
|
|
|
122
|
+
### Trailing slash
|
|
123
|
+
|
|
124
|
+
`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}`).
|
|
125
|
+
|
|
122
126
|
### Table of contents + reading time
|
|
123
127
|
|
|
124
128
|
`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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vdaluz/astro-blog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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',
|