feat: locale routing (en flat, zh subfolder), language switcher, dark default
Deploy / build (push) Successful in 11s
Deploy / build (push) Successful in 11s
- Derive lang from folder (posts/zh/* -> zh, else en); remove lang: frontmatter - Change post route to [...slug] to capture nested zh paths - Add nav language switcher (EN <-> 中文) + /zh/ landing page - Default theme to dark (light opt-in via toggle) - Translate hello-world-zh placeholder into English intro post - Update case study: docker-compose sample + real install gotchas (commit history) - Add docs/project-state.md (Tier 1/2/3 backlog)
This commit is contained in:
1 parent
a0b238bc98
commit
e0d4b0fbb7
17 files changed
+318
-44
No files matched your search
@@ -2,16 +2,17 @@
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import PostList from '../../components/PostList.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../../lib/lang';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en');
|
||||
const posts = await getCollection('posts', isEn);
|
||||
const cats = [...new Set(posts.map((p) => p.data.category))];
|
||||
return cats.map((cat) => ({ params: { category: cat } }));
|
||||
}
|
||||
|
||||
const { category } = Astro.params;
|
||||
|
||||
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en'))
|
||||
const posts = (await getCollection('posts', isEn))
|
||||
.filter((p) => p.data.category === category)
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
---
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../../lib/lang';
|
||||
|
||||
const posts = await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en');
|
||||
const posts = await getCollection('posts', isEn);
|
||||
|
||||
const categories = [...new Set(posts.map((p) => p.data.category))].sort();
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import PostList from '../components/PostList.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../lib/lang';
|
||||
import { SITE } from '../config';
|
||||
|
||||
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en'))
|
||||
const posts = (await getCollection('posts', isEn))
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
---
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import AuthorCard from '../../components/AuthorCard.astro';
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import { postLang } from '../../lib/lang';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts', ({ data }) => !data.draft);
|
||||
@@ -14,17 +15,19 @@ export async function getStaticPaths() {
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
|
||||
const lang = postLang(post.slug);
|
||||
|
||||
// Reading time (~200 wpm)
|
||||
const words = post.body?.trim().split(/\s+/).length ?? 0;
|
||||
const minutes = Math.max(1, Math.round(words / 200));
|
||||
|
||||
const fmt = (d: Date) =>
|
||||
d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
d.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
|
||||
// Related posts: same category or overlapping tags, excluding self
|
||||
// Related posts: same language, same category or overlapping tags, excluding self
|
||||
const all = await getCollection('posts', ({ data }) => !data.draft);
|
||||
const related = all
|
||||
.filter((p) => p.slug !== post.slug && p.data.lang === post.data.lang)
|
||||
.filter((p) => p.slug !== post.slug && postLang(p.slug) === lang)
|
||||
.map((p) => {
|
||||
const catMatch = p.data.category === post.data.category ? 2 : 0;
|
||||
const tagOverlap = p.data.tags.filter((t) => post.data.tags.includes(t)).length;
|
||||
@@ -39,7 +42,7 @@ const related = all
|
||||
<BaseLayout
|
||||
title={`${post.data.title} — Mr Hoelee`}
|
||||
description={post.data.description}
|
||||
lang={post.data.lang}
|
||||
lang={lang}
|
||||
ogImage={post.data.ogImage ?? '/og-default.png'}
|
||||
type="article"
|
||||
article={{
|
||||
@@ -69,7 +72,7 @@ const related = all
|
||||
|
||||
{related.length > 0 && (
|
||||
<section class="related">
|
||||
<h2>Related posts</h2>
|
||||
<h2>{lang === 'zh' ? '相关文章' : 'Related posts'}</h2>
|
||||
<div class="related-grid">
|
||||
{related.map((p) => (
|
||||
<a class="related-card" href={`/posts/${p.slug}/`}>
|
||||
@@ -2,8 +2,9 @@
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import PostList from '../../components/PostList.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../../lib/lang';
|
||||
|
||||
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en'))
|
||||
const posts = (await getCollection('posts', isEn))
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
---
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isEn } from '../lib/lang';
|
||||
import { SITE } from '../config';
|
||||
|
||||
export async function GET(context) {
|
||||
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.lang === 'en'))
|
||||
const posts = (await getCollection('posts', isEn))
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
|
||||
return rss({
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import PostList from '../../components/PostList.astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { isZh } from '../../lib/lang';
|
||||
import { SITE } from '../../config';
|
||||
|
||||
const posts = (await getCollection('posts', isZh))
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={`${SITE.handle} — 技术博客`}
|
||||
description="Hoelee 的技术博客中文版:工程、DevOps、自托管、AI 自动化与 Web3 实验。"
|
||||
lang="zh"
|
||||
>
|
||||
<section>
|
||||
<h1>你好,我是 {SITE.author}。</h1>
|
||||
<p>
|
||||
一名base在马来西亚的全栈开发者兼 DevOps 工程师。我构建 Web 应用,
|
||||
自托管一个约 140 个容器的 homelab,并运营面向马来西亚中小企业的
|
||||
邮箱托管业务。这里是博客的中文部分。
|
||||
</p>
|
||||
<p>
|
||||
<a href="/">切换到英文</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="/about/">关于我</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>最新文章</h2>
|
||||
{posts.length === 0 ? (
|
||||
<p><em>中文文章即将发布,敬请期待。</em></p>
|
||||
) : (
|
||||
<PostList posts={posts} />
|
||||
)}
|
||||
</section>
|
||||
</BaseLayout>
|
||||
Reference in new issue
Block a user