mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 11:41:17 +02:00
feat: implement internationalization (i18n) support
This commit is contained in:
parent
32ffec8480
commit
d6c98880d3
17 changed files with 247 additions and 18 deletions
|
@ -10,7 +10,7 @@ interface Props {
|
|||
const { postTitle, postDescription, postImage } = Astro.props
|
||||
const { title, subtitle, description, author, url, favicon } = themeConfig.site
|
||||
const { light: { backgroundStart: lightMode }, dark: { backgroundStart: darkMode } } = themeConfig.color
|
||||
const { language } = themeConfig.global
|
||||
const { locale, moreLocale } = themeConfig.global
|
||||
const { verification = {}, twitterID = '', facebookID = '', facebookLink = '', googleAnalyticsID = '', umamiAnalyticsID = '', siteScreenshot = '' } = themeConfig.seo ?? {}
|
||||
const { google = '', bing = '', yandex = '', baidu = '' } = verification
|
||||
const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '', customUmamiAnalyticsURL = '', customUmamiAnalyticsJS = '' } = themeConfig.preload
|
||||
|
@ -46,11 +46,18 @@ const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '',
|
|||
<link rel="author" href={url} />
|
||||
<link rel="publisher" href={author} />
|
||||
<link rel="canonical" href={Astro.url} />
|
||||
<!-- todo language -->
|
||||
<link rel="alternate" href="/en/" hreflang="en" />
|
||||
<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="RSS" />
|
||||
<link rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
||||
|
||||
<!-- i18n hreflang generate -->
|
||||
{[locale, ...moreLocale].map(lang => (
|
||||
<link
|
||||
rel="alternate"
|
||||
href={lang === locale ? '/' : `/${lang}/`}
|
||||
hreflang={lang === 'zh-tw' ? 'zh-TW' : lang}
|
||||
/>
|
||||
))}
|
||||
|
||||
<!-- Facebook Open Graph -->
|
||||
<meta property="fb:app_id" content={facebookID} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
|
@ -60,7 +67,7 @@ const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '',
|
|||
<meta property="og:image:alt" content={postTitle || title} />
|
||||
<meta property="og:description" content={postDescription || subtitle} />
|
||||
<meta property="og:site_name" content={title} />
|
||||
<meta property="og:locale" content={language} />
|
||||
<meta property="og:locale" content={Astro.currentLocale?.replace('-', '_') || 'en_US'} />
|
||||
<meta property="article:author" content={facebookLink} />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
|
|
|
@ -30,7 +30,8 @@ export const themeConfig: ThemeConfig = {
|
|||
|
||||
// GLOBAL SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> START
|
||||
global: {
|
||||
language: 'zh-CN', // en-US, zh-CN
|
||||
locale: 'zh', // zh, zh-tw, ja, en, es, ru, default locale setting
|
||||
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // ['zh-tw', 'ja', 'en', 'es', 'ru'], not fill in the default locale code again
|
||||
font: 'sans', // sans, serif, choose the font style for posts
|
||||
rss: true, // true, false, whether to enable RSS
|
||||
toc: true, // true, false, whether to enable table of contents in posts
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Head from '@/components/Head.astro'
|
||||
---
|
||||
|
||||
<html lang="zh-CN">
|
||||
<html lang={Astro.currentLocale || 'en-US'}>
|
||||
<head>
|
||||
<Head />
|
||||
</head>
|
||||
|
|
7
src/pages/[lang]/about.astro
Normal file
7
src/pages/[lang]/about.astro
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
---
|
||||
|
||||
<Layout>
|
||||
about me
|
||||
</Layout>
|
47
src/pages/[lang]/index.astro
Normal file
47
src/pages/[lang]/index.astro
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getPinnedPosts, getPosts } from '@/utils/content.config'
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{ params: { lang: 'zh' } },
|
||||
{ params: { lang: 'en' } },
|
||||
]
|
||||
}
|
||||
|
||||
const { lang } = Astro.params
|
||||
const pinnedPosts = await getPinnedPosts()
|
||||
const posts = await getPosts()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<main>
|
||||
{pinnedPosts.length > 0 && (
|
||||
<section>
|
||||
<ul>
|
||||
{pinnedPosts.map(post => (
|
||||
<li>
|
||||
<a href={`/${lang}/posts/${post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/${lang}/posts/${post.slug}/`}>
|
||||
{post.data.title}
|
||||
<time>({post.data.published.toISOString().split('T')[0]})</time>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</Layout>
|
23
src/pages/[lang]/posts/[slug].astro
Normal file
23
src/pages/[lang]/posts/[slug].astro
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('posts')
|
||||
return posts.map(post => ({
|
||||
params: { slug: post.slug },
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
|
||||
const { post } = Astro.props
|
||||
const { Content } = await post.render()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<article>
|
||||
<h1>{post.data.title}</h1>
|
||||
<time>{post.data.published.toISOString().split('T')[0]}</time>
|
||||
<Content />
|
||||
</article>
|
||||
</Layout>
|
36
src/pages/[lang]/tags/[tags].astro
Normal file
36
src/pages/[lang]/tags/[tags].astro
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllTags, getPostsByTag } from '@/utils/content.config'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const tags = await getAllTags()
|
||||
return tags.map(tag => ({
|
||||
params: { tags: tag },
|
||||
props: { tags: tag },
|
||||
}))
|
||||
}
|
||||
|
||||
const { tags } = Astro.props
|
||||
const posts = await getPostsByTag(tags)
|
||||
const allTags = await getAllTags()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{allTags.map(tag => (
|
||||
<a href={`/tags/${tag}/`}>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Layout>
|
15
src/pages/[lang]/tags/index.astro
Normal file
15
src/pages/[lang]/tags/index.astro
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllTags } from '@/utils/content.config'
|
||||
|
||||
const allTags = await getAllTags()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{allTags.map(tag => (
|
||||
<a href={`/tags/${tag}/`}>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
7
src/pages/about.astro
Normal file
7
src/pages/about.astro
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
---
|
||||
|
||||
<Layout>
|
||||
about me
|
||||
</Layout>
|
|
@ -10,7 +10,6 @@ const pinnedPosts = await getPinnedPosts()
|
|||
<main>
|
||||
{pinnedPosts.length > 0 && (
|
||||
<section>
|
||||
<h2>置顶文章</h2>
|
||||
<ul>
|
||||
{pinnedPosts.map(post => (
|
||||
<li>
|
||||
|
@ -25,7 +24,6 @@ const pinnedPosts = await getPinnedPosts()
|
|||
)}
|
||||
|
||||
<section>
|
||||
<h2>所有文章</h2>
|
||||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
|
|
|
@ -8,7 +8,7 @@ import sanitizeHtml from 'sanitize-html'
|
|||
|
||||
const parser = new MarkdownIt()
|
||||
const { title, description, url } = themeConfig.site
|
||||
const { language } = themeConfig.global
|
||||
const { locale } = themeConfig.global
|
||||
const followConfig = themeConfig.seo?.follow
|
||||
|
||||
// Extract first 100 chars from content as description
|
||||
|
@ -49,7 +49,7 @@ export async function GET(_context: APIContext) {
|
|||
),
|
||||
})),
|
||||
customData: `
|
||||
<language>${language}</language>
|
||||
<language>${locale}</language>
|
||||
${followConfig?.feedID && followConfig?.userID
|
||||
? `<follow_challenge>
|
||||
<feedId>${followConfig.feedID}</feedId>
|
||||
|
|
36
src/pages/tags/[tags].astro
Normal file
36
src/pages/tags/[tags].astro
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllTags, getPostsByTag } from '@/utils/content.config'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const tags = await getAllTags()
|
||||
return tags.map(tag => ({
|
||||
params: { tags: tag },
|
||||
props: { tags: tag },
|
||||
}))
|
||||
}
|
||||
|
||||
const { tags } = Astro.props
|
||||
const posts = await getPostsByTag(tags)
|
||||
const allTags = await getAllTags()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{allTags.map(tag => (
|
||||
<a href={`/tags/${tag}/`}>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
{posts.map(post => (
|
||||
<li>
|
||||
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Layout>
|
15
src/pages/tags/index.astro
Normal file
15
src/pages/tags/index.astro
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllTags } from '@/utils/content.config'
|
||||
|
||||
const allTags = await getAllTags()
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div>
|
||||
{allTags.map(tag => (
|
||||
<a href={`/tags/${tag}/`}>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
5
src/types/index.d.ts
vendored
5
src/types/index.d.ts
vendored
|
@ -1,3 +1,5 @@
|
|||
import type { langPath } from '@/utils/ui'
|
||||
|
||||
export interface ThemeConfig {
|
||||
|
||||
site: {
|
||||
|
@ -24,7 +26,8 @@ export interface ThemeConfig {
|
|||
}
|
||||
|
||||
global: {
|
||||
language: string
|
||||
locale: typeof langPath[number]
|
||||
moreLocale: typeof langPath[number][]
|
||||
font: string
|
||||
rss: boolean
|
||||
toc: boolean
|
||||
|
|
|
@ -1,30 +1,44 @@
|
|||
export const language = {
|
||||
zh: {
|
||||
// Global Language Map
|
||||
export const langMap: Record<string, string[]> = {
|
||||
'zh': ['zh-CN'],
|
||||
'zh-tw': ['zh-TW'],
|
||||
'ja': ['ja-JP'],
|
||||
'en': ['en-US'],
|
||||
'es': ['es-ES'],
|
||||
'ru': ['ru-RU'],
|
||||
}
|
||||
// Standard Language Code
|
||||
export const langCode = Object.values(langMap).flat()
|
||||
// Abbreviated Language Code
|
||||
export const langPath = Object.keys(langMap).flat()
|
||||
// UI Translation
|
||||
export const ui = {
|
||||
'zh': {
|
||||
posts: '文章',
|
||||
tags: '标签',
|
||||
about: '关于',
|
||||
},
|
||||
tw: {
|
||||
'zh-tw': {
|
||||
posts: '文章',
|
||||
tags: '標籤',
|
||||
about: '關於',
|
||||
},
|
||||
ja: {
|
||||
'ja': {
|
||||
posts: '記事',
|
||||
tags: 'タグ',
|
||||
about: '概要',
|
||||
},
|
||||
en: {
|
||||
'en': {
|
||||
posts: 'Posts',
|
||||
tags: 'Tags',
|
||||
about: 'About',
|
||||
},
|
||||
es: {
|
||||
'es': {
|
||||
posts: 'Posts',
|
||||
tags: 'Tags',
|
||||
about: 'Sobre',
|
||||
},
|
||||
ru: {
|
||||
'ru': {
|
||||
posts: 'Посты',
|
||||
tags: 'Теги',
|
||||
about: 'О себе',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue