refactor: optimize all i18n route pages

This commit is contained in:
radishzzz 2025-03-17 18:15:17 +00:00
parent 0888b59c5f
commit 473b13d0ab
8 changed files with 125 additions and 133 deletions

8
pnpm-lock.yaml generated
View file

@ -1818,8 +1818,8 @@ packages:
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
eslint-plugin-jsdoc@50.6.7: eslint-plugin-jsdoc@50.6.8:
resolution: {integrity: sha512-8JrJRI6oSCHAdr5MvOD1L8nwywmiusk5RKfTisqq2rN5t65QmzmfBzAUkK0lbvwZ442HN33x+IbUon8d+axKoA==} resolution: {integrity: sha512-PPZVqhoXaalMQwDGzcQrJtPSPIPOYsSMtvkjYAdsIazOW20yhYtVX4+jLL+XznD4zYTXyZbPWPRKkNev4D4lyw==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
@ -3862,7 +3862,7 @@ snapshots:
eslint-plugin-antfu: 3.1.1(eslint@9.22.0(jiti@2.4.2)) eslint-plugin-antfu: 3.1.1(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-command: 3.1.0(eslint@9.22.0(jiti@2.4.2)) eslint-plugin-command: 3.1.0(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-import-x: 4.8.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2) eslint-plugin-import-x: 4.8.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-jsdoc: 50.6.7(eslint@9.22.0(jiti@2.4.2)) eslint-plugin-jsdoc: 50.6.8(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-jsonc: 2.19.1(eslint@9.22.0(jiti@2.4.2)) eslint-plugin-jsonc: 2.19.1(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-n: 17.16.2(eslint@9.22.0(jiti@2.4.2)) eslint-plugin-n: 17.16.2(eslint@9.22.0(jiti@2.4.2))
eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-no-only-tests: 3.3.0
@ -5769,7 +5769,7 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
eslint-plugin-jsdoc@50.6.7(eslint@9.22.0(jiti@2.4.2)): eslint-plugin-jsdoc@50.6.8(eslint@9.22.0(jiti@2.4.2)):
dependencies: dependencies:
'@es-joy/jsdoccomment': 0.49.0 '@es-joy/jsdoccomment': 0.49.0
are-docs-informative: 0.0.2 are-docs-informative: 0.0.2

View file

@ -2,6 +2,18 @@ import { defaultLocale, moreLocales } from '@/config'
import { getLangFromPath, getNextGlobalLang } from '@/i18n/lang' import { getLangFromPath, getNextGlobalLang } from '@/i18n/lang'
import { cleanPath } from '@/utils/page' import { cleanPath } from '@/utils/page'
/**
* Get path to tag page with language support
* @param tagName Tag name
* @param lang Current language code
* @returns Path to tag page
*/
export function getTagPath(tagName: string, lang: string): string {
return lang === defaultLocale
? `/tags/${tagName}/`
: `/${lang}/tags/${tagName}/`
}
// Generates a localized path based on current language // Generates a localized path based on current language
export function getLocalizedPath(path: string, currentLang?: string) { export function getLocalizedPath(path: string, currentLang?: string) {
const clean = cleanPath(path) const clean = cleanPath(path)

View file

@ -1,5 +1,5 @@
--- ---
import { allLocales, defaultLocale } from '@/config' import { defaultLocale, moreLocales } from '@/config'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
@ -13,18 +13,16 @@ export async function getStaticPaths() {
// Default locale // Default locale
paths.push({ paths.push({
params: { about: 'about' }, params: { about: 'about/' },
props: { lang: defaultLocale }, props: { lang: defaultLocale },
}) })
// More locales // More locales
allLocales.forEach((lang: string) => { moreLocales.forEach((lang: string) => {
if (lang !== defaultLocale) { paths.push({
paths.push({ params: { about: `${lang}/about/` },
params: { about: `${lang}/about` }, props: { lang },
props: { lang }, })
})
}
}) })
return paths return paths
@ -32,6 +30,7 @@ export async function getStaticPaths() {
const { lang } = Astro.props const { lang } = Astro.props
// Get about page content with different language
const allAboutEntries = await getCollection('about') const allAboutEntries = await getCollection('about')
const aboutEntry = allAboutEntries.find(entry => entry.data.lang === lang) const aboutEntry = allAboutEntries.find(entry => entry.data.lang === lang)
|| allAboutEntries.find(entry => entry.data.lang === '') || allAboutEntries.find(entry => entry.data.lang === '')

View file

@ -1,11 +1,10 @@
--- ---
import PostList from '@/components/PostList.astro' import PostList from '@/components/PostList.astro'
import { allLocales, defaultLocale } from '@/config' import { defaultLocale, moreLocales } from '@/config'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { getPinnedPosts, getPostsByYear } from '@/utils/content' import { getPinnedPosts, getPostsByYear } from '@/utils/content'
export async function getStaticPaths() { export async function getStaticPaths() {
// 定义路径数组的类型
type PathItem = { type PathItem = {
params: { index: string | undefined } params: { index: string | undefined }
props: { lang: string } props: { lang: string }
@ -13,31 +12,31 @@ export async function getStaticPaths() {
const paths: PathItem[] = [] const paths: PathItem[] = []
// 默认语言的首页 // Default locale
paths.push({ paths.push({
params: { index: undefined }, params: { index: undefined },
props: { lang: defaultLocale }, props: { lang: defaultLocale },
}) })
// 更多语言的首页 // More locales
allLocales.forEach((lang: string) => { moreLocales.forEach((lang: string) => {
if (lang !== defaultLocale) { paths.push({
paths.push({ params: { index: `${lang}/` },
params: { index: lang }, props: { lang },
props: { lang }, })
})
}
}) })
return paths return paths
} }
const { lang } = Astro.props const { lang } = Astro.props
const pinnedPosts = await getPinnedPosts(lang) const pinnedPosts = await getPinnedPosts(lang)
const postsByYear = await getPostsByYear(lang) const postsByYear = await getPostsByYear(lang)
--- ---
<Layout> <Layout>
<main> <main>
<!-- Pinned Posts --> <!-- Pinned Posts -->
{pinnedPosts.length > 0 && ( {pinnedPosts.length > 0 && (
<section class="mb-7.5 lg:mb-9.5"> <section class="mb-7.5 lg:mb-9.5">
@ -45,6 +44,7 @@ const postsByYear = await getPostsByYear(lang)
<PostList posts={pinnedPosts} lang={lang} /> <PostList posts={pinnedPosts} lang={lang} />
</section> </section>
)} )}
<!-- Regular Posts --> <!-- Regular Posts -->
{[...postsByYear.entries()].map(([_year, posts]) => ( {[...postsByYear.entries()].map(([_year, posts]) => (
<section class="mb-7.5 lg:mb-9.5"> <section class="mb-7.5 lg:mb-9.5">
@ -52,5 +52,6 @@ const postsByYear = await getPostsByYear(lang)
<PostList posts={posts} lang={lang} /> <PostList posts={posts} lang={lang} />
</section> </section>
))} ))}
</main> </main>
</Layout> </Layout>

View file

@ -2,52 +2,48 @@
import type { CollectionEntry } from 'astro:content' import type { CollectionEntry } from 'astro:content'
import Comments from '@/components/Comments/index.astro' import Comments from '@/components/Comments/index.astro'
import PostDate from '@/components/PostDate.astro' import PostDate from '@/components/PostDate.astro'
import { allLocales, defaultLocale } from '@/config' import { allLocales, defaultLocale, moreLocales } from '@/config'
import { getTagPath } from '@/i18n/path'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { checkSlugDuplication } from '@/utils/content' import { checkPostSlugDuplication } from '@/utils/content'
import { generateDescription } from '@/utils/description' import { generateDescription } from '@/utils/description'
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection('posts') const posts = await getCollection('posts')
const duplicates = await checkSlugDuplication(posts) // Check if there are duplicate post slugs
const duplicates = await checkPostSlugDuplication(posts)
if (duplicates.length > 0) { if (duplicates.length > 0) {
throw new Error(`Slug conflicts found:\n${duplicates.join('\n')}`) throw new Error(`Duplicate post slugs:\n${duplicates.join('\n')}`)
} }
// 创建slug到语言的映射 // Use a Map to store the relationship between post slugs and their supported languages
const slugToLangs: Record<string, string[]> = {} // Set is used to store the supported languages for each post
const slugToLangsMap = posts.reduce((map, post) => {
// 填充映射
posts.forEach((post: CollectionEntry<'posts'>) => {
const slug = post.data.abbrlink || post.slug const slug = post.data.abbrlink || post.slug
const lang = post.data.lang || defaultLocale const lang = post.data.lang
// 如果文章没有指定语言,初始化为所有支持的语言 if (!map.has(slug)) {
if (!slugToLangs[slug]) { map.set(slug, new Set(lang ? [lang] : allLocales))
if (!post.data.lang) { }
slugToLangs[slug] = [...allLocales] // 文章支持所有语言 else if (lang) {
} map.get(slug)?.add(lang)
else {
slugToLangs[slug] = [defaultLocale] // 仅默认语言和指定语言
}
} }
if (!slugToLangs[slug].includes(lang)) { return map
slugToLangs[slug].push(lang) }, new Map<string, Set<string>>())
}
})
// 对每个文章的supportedLangs按照allLocales的顺序排序 // Convert Map<slug, Set<langs>> to Record<slug, langs[]> structure
Object.keys(slugToLangs).forEach((slug) => { // Sort languages according to the order defined in allLocales
// 按照allLocales的顺序排序 const slugToLangs = Object.fromEntries(
slugToLangs[slug].sort((a, b) => { Array.from(slugToLangsMap.entries()).map(([slug, langs]) => [
return allLocales.indexOf(a) - allLocales.indexOf(b) slug,
}) [...langs].sort((a, b) => allLocales.indexOf(a) - allLocales.indexOf(b)),
}) ]),
)
// 定义路径数组的类型 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
type PathItem = { type PathItem = {
params: { posts_slug: string } params: { posts_slug: string }
props: { post: any, lang: string, supportedLangs: string[] } props: { post: any, lang: string, supportedLangs: string[] }
@ -55,16 +51,16 @@ export async function getStaticPaths() {
const paths: PathItem[] = [] const paths: PathItem[] = []
// 默认语言的文章页面 (没有语言前缀) // Default locale
posts.forEach((post: CollectionEntry<'posts'>) => { posts.forEach((post: CollectionEntry<'posts'>) => {
// Show drafts in dev mode only
if (import.meta.env.DEV || !post.data.draft) { if (import.meta.env.DEV || !post.data.draft) {
const slug = post.data.abbrlink || post.slug const slug = post.data.abbrlink || post.slug
const postLang = post.data.lang || defaultLocale const lang = post.data.lang
// 只有当文章语言是默认语言或没有指定语言时才生成默认语言路径 if (lang === defaultLocale || lang === '') {
if (postLang === defaultLocale || post.data.lang === '') {
paths.push({ paths.push({
params: { posts_slug: `posts/${slug}` }, params: { posts_slug: `posts/${slug}/` },
props: { props: {
post, post,
lang: defaultLocale, lang: defaultLocale,
@ -75,23 +71,22 @@ export async function getStaticPaths() {
} }
}) })
// 更多语言的文章页面 (有语言前缀) // More locales
allLocales.forEach((lang: string) => { moreLocales.forEach((lang: string) => {
if (lang !== defaultLocale) { posts.forEach((post: CollectionEntry<'posts'>) => {
posts.forEach((post: CollectionEntry<'posts'>) => { // Process posts with matching language or no language specified
if ((import.meta.env.DEV || !post.data.draft) && (post.data.lang === lang || post.data.lang === '')) { if ((import.meta.env.DEV || !post.data.draft) && (post.data.lang === lang || post.data.lang === '')) {
const slug = post.data.abbrlink || post.slug const slug = post.data.abbrlink || post.slug
paths.push({ paths.push({
params: { posts_slug: `${lang}/posts/${slug}` }, params: { posts_slug: `${lang}/posts/${slug}/` },
props: { props: {
post, post,
lang, lang,
supportedLangs: slugToLangs[slug] || [], supportedLangs: slugToLangs[slug] || [],
}, },
}) })
} }
}) })
}
}) })
return paths return paths
@ -100,13 +95,6 @@ export async function getStaticPaths() {
const { post, lang, supportedLangs } = Astro.props const { post, lang, supportedLangs } = Astro.props
const description = generateDescription(post, 'meta') const description = generateDescription(post, 'meta')
const { Content, remarkPluginFrontmatter } = await post.render() const { Content, remarkPluginFrontmatter } = await post.render()
// 构建标签链接
function getTagUrl(tagName: string): string {
return lang === defaultLocale
? `/tags/${tagName}/`
: `/${lang}/tags/${tagName}/`
}
--- ---
<Layout <Layout
@ -148,7 +136,7 @@ function getTagUrl(tagName: string): string {
<div class="uno-tags-wrapper"> <div class="uno-tags-wrapper">
{post.data.tags.map((tag: string) => ( {post.data.tags.map((tag: string) => (
<a <a
href={getTagUrl(tag)} href={getTagPath(tag, lang)}
class="uno-tags-style" class="uno-tags-style"
> >
{tag} {tag}

View file

@ -1,10 +1,10 @@
--- ---
import { allLocales, defaultLocale } from '@/config' import { defaultLocale, moreLocales } from '@/config'
import { getTagPath } from '@/i18n/path'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { getAllTags } from '@/utils/content' import { getAllTags } from '@/utils/content'
export async function getStaticPaths() { export async function getStaticPaths() {
// 定义路径数组的类型
type PathItem = { type PathItem = {
params: { tags: string } params: { tags: string }
props: { lang: string } props: { lang: string }
@ -12,20 +12,18 @@ export async function getStaticPaths() {
const paths: PathItem[] = [] const paths: PathItem[] = []
// 默认语言的标签索引页 // Default locale
paths.push({ paths.push({
params: { tags: 'tags' }, params: { tags: 'tags/' },
props: { lang: defaultLocale }, props: { lang: defaultLocale },
}) })
// 更多语言的标签索引页 // More locales
allLocales.forEach((lang: string) => { moreLocales.forEach((lang: string) => {
if (lang !== defaultLocale) { paths.push({
paths.push({ params: { tags: `${lang}/tags/` },
params: { tags: `${lang}/tags` }, props: { lang },
props: { lang }, })
})
}
}) })
return paths return paths
@ -33,24 +31,17 @@ export async function getStaticPaths() {
const { lang } = Astro.props const { lang } = Astro.props
const allTags = await getAllTags(lang) const allTags = await getAllTags(lang)
// 构建标签链接
function getTagUrl(tagName: string): string {
return lang === defaultLocale
? `/tags/${tagName}/`
: `/${lang}/tags/${tagName}/`
}
--- ---
<Layout> <Layout>
<div class="uno-decorative-line"></div> <div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper"> <div class="uno-tags-wrapper">
{allTags.map(tagName => ( {allTags.map(tag => (
<a <a
href={getTagUrl(tagName)} href={getTagPath(tag, lang)}
class="uno-tags-style" class="uno-tags-style"
> >
{tagName} {tag}
</a> </a>
))} ))}
</div> </div>

View file

@ -1,11 +1,11 @@
--- ---
import PostList from '@/components/PostList.astro' import PostList from '@/components/PostList.astro'
import { allLocales, defaultLocale } from '@/config' import { allLocales, defaultLocale, moreLocales } from '@/config'
import { getTagPath } from '@/i18n/path'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
import { getAllTags, getPostsByTag } from '@/utils/content' import { getAllTags, getPostsByTag } from '@/utils/content'
export async function getStaticPaths() { export async function getStaticPaths() {
// 定义路径数组的类型
type PathItem = { type PathItem = {
params: { tags_tag: string } params: { tags_tag: string }
props: { tag: string, lang: string } props: { tag: string, lang: string }
@ -13,26 +13,24 @@ export async function getStaticPaths() {
const paths: PathItem[] = [] const paths: PathItem[] = []
// 默认语言的标签页面 (没有语言前缀) // Default locale
const defaultTags = await getAllTags(defaultLocale) const defaultTags = await getAllTags(defaultLocale)
defaultTags.forEach((tag: string) => { defaultTags.forEach((tag: string) => {
paths.push({ paths.push({
params: { tags_tag: `tags/${tag}` }, params: { tags_tag: `tags/${tag}/` },
props: { tag, lang: defaultLocale }, props: { tag, lang: defaultLocale },
}) })
}) })
// 更多语言的标签页面 (有语言前缀) // More locales
for (const lang of allLocales) { for (const lang of moreLocales) {
if (lang !== defaultLocale) { const langTags = await getAllTags(lang)
const langTags = await getAllTags(lang) langTags.forEach((tag: string) => {
langTags.forEach((tag: string) => { paths.push({
paths.push({ params: { tags_tag: `${lang}/tags/${tag}/` },
params: { tags_tag: `${lang}/tags/${tag}` }, props: { tag, lang },
props: { tag, lang },
})
}) })
} })
} }
return paths return paths
@ -42,22 +40,16 @@ const { tag, lang } = Astro.props
const posts = await getPostsByTag(tag, lang) const posts = await getPostsByTag(tag, lang)
const allTags = await getAllTags(lang) const allTags = await getAllTags(lang)
// 获取当前标签在每种语言下是否有文章 // Check if tag has posts in each language, return language code if exists, null if not
const tagSupportedLangs = await Promise.all( const tagSupportedLangs = await Promise.all(
allLocales.map(async (locale) => { allLocales.map(async (locale) => {
const postsInLang = await getPostsByTag(tag, locale) const postsInLang = await getPostsByTag(tag, locale)
return postsInLang.length > 0 ? locale : null return postsInLang.length > 0 ? locale : null
}), }),
) )
// 过滤出支持当前标签的语言列表
const supportedLangs = tagSupportedLangs.filter(Boolean) as string[]
// 构建标签链接 // Filter to get supported languages
function getTagUrl(tagName: string): string { const supportedLangs = tagSupportedLangs.filter(Boolean) as string[]
return lang === defaultLocale
? `/tags/${tagName}/`
: `/${lang}/tags/${tagName}/`
}
--- ---
<Layout supportedLangs={supportedLangs}> <Layout supportedLangs={supportedLangs}>
@ -65,7 +57,7 @@ function getTagUrl(tagName: string): string {
<div class="uno-tags-wrapper"> <div class="uno-tags-wrapper">
{allTags.map(tagName => ( {allTags.map(tagName => (
<a <a
href={getTagUrl(tagName)} href={getTagPath(tagName, lang)}
class={`uno-tags-style ${ class={`uno-tags-style ${
tag === tagName tag === tagName
? 'border-secondary/75 text-primary' ? 'border-secondary/75 text-primary'

View file

@ -18,8 +18,12 @@ async function getPostMeta(post: CollectionEntry<'posts'>): Promise<Post> {
return { ...post, remarkPluginFrontmatter } return { ...post, remarkPluginFrontmatter }
} }
// Check if the slug is duplicated under the same language /**
export async function checkSlugDuplication(posts: Post[]): Promise<string[]> { * Check if the post slug is duplicated under the same language
* @param posts Array of blog posts
* @returns Array of duplicate slugs with language information
*/
export async function checkPostSlugDuplication(posts: Post[]): Promise<string[]> {
const slugMap = new Map<string, Set<string>>() const slugMap = new Map<string, Set<string>>()
const duplicates: string[] = [] const duplicates: string[] = []
@ -33,7 +37,12 @@ export async function checkSlugDuplication(posts: Post[]): Promise<string[]> {
const slugSet = slugMap.get(lang)! const slugSet = slugMap.get(lang)!
if (slugSet.has(slug)) { if (slugSet.has(slug)) {
duplicates.push(`Duplicate slug "${slug}" found in language "${lang || 'default'}"`) if (!lang) {
duplicates.push(`Duplicate slug "${slug}" found in universal post (applies to all languages)`)
}
else {
duplicates.push(`Duplicate slug "${slug}" found in "${lang}" language post`)
}
} }
else { else {
slugSet.add(slug) slugSet.add(slug)