Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,25 @@ export interface Translations {
}
series: {
description: string
summary: (postCount: number, seriesCount: number) => string
searchPlaceholder: string
sortRecent: string
sortCount: string
sortName: string
reset: string
selectedLabel: (series: string) => string
topSeries: string
topSeriesNote: string
topicGroups: string
ungrouped: string
postCount: (count: number, date: string) => string
postCountShort: (count: number) => string
fallbackDescription: (series: string) => string
readingPath: string
relatedTags: string
startReading: string
noSeries: string
noMatchingSeries: string
allSeries: string
noPostsInSeries: string
}
Expand Down Expand Up @@ -270,8 +287,25 @@ export const ko: Translations = {
},
series: {
description: "시리즈별 블로그 글 목록",
summary: (postCount, seriesCount) => `${postCount}개의 글이 ${seriesCount}개의 시리즈로 정리되어 있습니다.`,
searchPlaceholder: "시리즈, 태그, 설명으로 검색",
sortRecent: "최근순",
sortCount: "글 많은순",
sortName: "이름순",
reset: "초기화",
selectedLabel: (series) => `선택됨 · ${series}`,
topSeries: "주요 시리즈",
topSeriesNote: "최근 업데이트와 글 수 기준",
topicGroups: "주제 그룹",
ungrouped: "기타",
postCount: (count, date) => `${count}개의 글 · ${date}`,
postCountShort: (count) => `${count}편`,
fallbackDescription: (series) => `${series} 시리즈의 글을 순서대로 모아 봅니다.`,
readingPath: "읽기 순서",
relatedTags: "관련 태그",
startReading: "첫 글부터 읽기",
noSeries: "시리즈가 없습니다.",
noMatchingSeries: "조건에 맞는 시리즈가 없습니다.",
allSeries: "전체 시리즈",
noPostsInSeries: "해당 시리즈의 글이 없습니다.",
},
Expand Down Expand Up @@ -463,8 +497,25 @@ export const en: Translations = {
},
series: {
description: "Blog posts by series",
summary: (postCount, seriesCount) => `${postCount} post${postCount !== 1 ? "s" : ""} are organized into ${seriesCount} series.`,
searchPlaceholder: "Search series, tags, or descriptions",
sortRecent: "Recent",
sortCount: "Most posts",
sortName: "A-Z",
reset: "Reset",
selectedLabel: (series) => `Selected · ${series}`,
topSeries: "Top Series",
topSeriesNote: "Sorted by recent updates and post count",
topicGroups: "Topic Groups",
ungrouped: "Other",
postCount: (count, date) => `${count} post${count !== 1 ? "s" : ""} · ${date}`,
postCountShort: (count) => `${count} post${count !== 1 ? "s" : ""}`,
fallbackDescription: (series) => `Read the ${series} series in order.`,
readingPath: "Reading Path",
relatedTags: "Related Tags",
startReading: "Start reading",
noSeries: "No series yet.",
noMatchingSeries: "No series match these filters.",
allSeries: "All series",
noPostsInSeries: "No posts in this series.",
},
Expand Down
52 changes: 44 additions & 8 deletions src/lib/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,32 +198,68 @@ export interface SeriesInfo {
name: string
postCount: number
firstDate: string
latestDate: string
description: string
tags: string[]
posts: PostMeta[]
}

export function getAllSeries(language: Language = "ko"): SeriesInfo[] {
const posts = getAllPosts(language)
const seriesMap = new Map<string, { count: number; firstDate: string }>()
const seriesMap = new Map<string, { posts: PostMeta[]; firstDate: string; latestDate: string }>()

for (const post of posts) {
if (!post.series) continue
const existing = seriesMap.get(post.series)
if (existing) {
existing.count++
existing.posts.push(post)
if (post.date < existing.firstDate) existing.firstDate = post.date
if (post.date > existing.latestDate) existing.latestDate = post.date
} else {
seriesMap.set(post.series, { count: 1, firstDate: post.date })
seriesMap.set(post.series, { posts: [post], firstDate: post.date, latestDate: post.date })
}
}

return [...seriesMap.entries()]
.map(([name, { count, firstDate }]) => ({ name, postCount: count, firstDate }))
.sort((a, b) => (a.firstDate > b.firstDate ? -1 : 1))
.map(([name, { posts, firstDate, latestDate }]) => {
const orderedPosts = sortSeriesPosts(posts)
const tagCounts = new Map<string, number>()
for (const post of orderedPosts) {
for (const tag of post.tags) {
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1)
}
}

return {
name,
postCount: orderedPosts.length,
firstDate,
latestDate,
description: orderedPosts[0]?.description ?? "",
tags: [...tagCounts.entries()]
.sort(([a, aCount], [b, bCount]) => bCount - aCount || a.localeCompare(b))
.map(([tag]) => tag),
posts: orderedPosts,
}
})
.sort((a, b) => {
if (a.latestDate !== b.latestDate) return b.latestDate.localeCompare(a.latestDate)
return a.name.localeCompare(b.name)
})
}

export function getSeriesPosts(seriesName: string, language: Language = "ko"): PostMeta[] {
return getAllPosts(language)
.filter((p) => p.series === seriesName)
.sort((a, b) => (a.seriesOrder ?? 0) - (b.seriesOrder ?? 0))
return sortSeriesPosts(getAllPosts(language).filter((p) => p.series === seriesName))
}

function sortSeriesPosts(posts: PostMeta[]): PostMeta[] {
return [...posts].sort((a, b) => {
const aOrder = a.seriesOrder ?? Number.MAX_SAFE_INTEGER
const bOrder = b.seriesOrder ?? Number.MAX_SAFE_INTEGER
if (aOrder !== bOrder) return aOrder - bOrder
if (a.date !== b.date) return a.date > b.date ? -1 : 1
return a.title.localeCompare(b.title)
})
}

export function getAdjacentPosts(slug: string, language: Language = "ko"): { prev: PostMeta | null; next: PostMeta | null } {
Expand Down
Loading
Loading