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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user