feat(blog-i18n): localize blog UI & SEO; add tag pages to sitemap

- Add Messages.meta.blog and text.blog (BlogTexts) to types/messages
  - Update all locales with blog UI strings and meta.blog
  - Localize blog list, tag pages, and article detail (titles, labels, dates)
  - Pass messages to ArticleListItem; TableOfContents supports localized title
  - Use dictionary-based metadata; alternates cover all supported locales
  - Sitemap: include /[lang]/blog/tag/{tag} and set blog list lastModified to newest post
  - JSON-LD: hardcode site URL in getSiteUrl() for consistency
This commit is contained in:
david_bai
2025-11-22 10:37:29 +08:00
parent 18f6703c6b
commit 89a38936b6
17 changed files with 282 additions and 64 deletions
+10 -4
View File
@@ -1,13 +1,15 @@
import Link from "next/link";
import Image from "next/image";
import { type BlogPost } from "@/lib/blog";
import { Messages } from "@/types/messages";
interface ArticleListItemProps {
post: BlogPost;
lang: string;
messages: Messages;
}
export function ArticleListItem({ post, lang }: ArticleListItemProps) {
export function ArticleListItem({ post, lang, messages }: ArticleListItemProps) {
return (
<article className="bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow overflow-hidden">
<div className="relative h-80 w-full">
@@ -24,7 +26,11 @@ export function ArticleListItem({ post, lang }: ArticleListItemProps) {
<div className="p-8">
<div className="flex items-center gap-4 text-sm text-gray-500 mb-4">
<time className="font-medium">
{new Date(post.frontmatter.date).toLocaleDateString()}
{new Date(post.frontmatter.date).toLocaleDateString(lang, {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
<span>·</span>
<div className="flex gap-2 flex-wrap">
@@ -53,7 +59,7 @@ export function ArticleListItem({ post, lang }: ArticleListItemProps) {
href={`/${lang}/blog/${post.slug}`}
className="text-blue-600 hover:text-blue-800 font-medium inline-flex items-center text-lg"
>
Read more
{messages.text.blog.read_more}
<svg
className="w-5 h-5 ml-2"
viewBox="0 0 24 24"
@@ -71,7 +77,7 @@ export function ArticleListItem({ post, lang }: ArticleListItemProps) {
<div className="flex items-center gap-3">
<span className="text-sm">
by <span className="font-bold">{post.frontmatter.author}</span>
{messages.text.blog.by} <span className="font-bold">{post.frontmatter.author}</span>
</span>
</div>
</div>
+3 -1
View File
@@ -10,10 +10,12 @@ interface TocItem {
interface TableOfContentsProps {
content: string;
title?: string;
}
export const TableOfContents: React.FC<TableOfContentsProps> = ({
content,
title = "Table of contents",
}) => {
const [activeId, setActiveId] = useState<string>("");
const [toc, setToc] = useState<TocItem[]>([]);
@@ -110,7 +112,7 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({
return (
<nav className="hidden lg:block sticky top-8 p-6 bg-gray-50 rounded-lg max-h-[calc(100vh-4rem)] overflow-y-auto">
<h4 className="text-lg font-semibold mb-4">Table of contents</h4>
<h4 className="text-lg font-semibold mb-4">{title}</h4>
<ul className="space-y-2">
{toc.map((item) => (
<li