From d6c98880d35dc67ccd9f6958b646fd7ec7000889 Mon Sep 17 00:00:00 2001 From: radishzzz Date: Sat, 18 Jan 2025 00:01:25 +0000 Subject: [PATCH] feat: implement internationalization (i18n) support --- astro.config.ts | 9 ++++++ public/image/astro-icon.svg | 11 +++++++ src/components/Head.astro | 15 ++++++--- src/config/index.ts | 3 +- src/layouts/Layout.astro | 2 +- src/pages/[lang]/about.astro | 7 +++++ src/pages/[lang]/index.astro | 47 +++++++++++++++++++++++++++++ src/pages/[lang]/posts/[slug].astro | 23 ++++++++++++++ src/pages/[lang]/tags/[tags].astro | 36 ++++++++++++++++++++++ src/pages/[lang]/tags/index.astro | 15 +++++++++ src/pages/about.astro | 7 +++++ src/pages/index.astro | 2 -- src/pages/rss.xml.ts | 4 +-- src/pages/tags/[tags].astro | 36 ++++++++++++++++++++++ src/pages/tags/index.astro | 15 +++++++++ src/types/index.d.ts | 5 ++- src/utils/ui.ts | 28 ++++++++++++----- 17 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 public/image/astro-icon.svg create mode 100644 src/pages/[lang]/about.astro create mode 100644 src/pages/[lang]/index.astro create mode 100644 src/pages/[lang]/posts/[slug].astro create mode 100644 src/pages/[lang]/tags/[tags].astro create mode 100644 src/pages/[lang]/tags/index.astro create mode 100644 src/pages/about.astro create mode 100644 src/pages/tags/[tags].astro create mode 100644 src/pages/tags/index.astro diff --git a/astro.config.ts b/astro.config.ts index 551fa7e..b003d41 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -28,13 +28,22 @@ import { GithubCardComponent } from './src/plugins/rehype-component-github-card. import { parseDirectiveNode } from './src/plugins/remark-directive-rehype.js' import { remarkExcerpt } from './src/plugins/remark-excerpt.js' import { remarkReadingTime } from './src/plugins/remark-reading-time.mjs' +import { langMap } from './src/utils/ui' const { url }: { url: ThemeConfig['site']['url'] } = themeConfig.site +const { locale }: { locale: ThemeConfig['global']['locale'] } = themeConfig.global export default defineConfig({ site: url, base: '/', trailingSlash: 'always', + i18n: { + locales: Object.entries(langMap).map(([path, codes]) => ({ + path, + codes, + })), + defaultLocale: locale, + }, integrations: [ partytown({ config: { diff --git a/public/image/astro-icon.svg b/public/image/astro-icon.svg new file mode 100644 index 0000000..52b76d5 --- /dev/null +++ b/public/image/astro-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/Head.astro b/src/components/Head.astro index 0388109..800d25c 100644 --- a/src/components/Head.astro +++ b/src/components/Head.astro @@ -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 = '', - - + +{[locale, ...moreLocale].map(lang => ( + +))} + @@ -60,7 +67,7 @@ const { cdn, commentURL = '', imageHostURL = '', customGoogleAnalyticsURL = '', - + diff --git a/src/config/index.ts b/src/config/index.ts index 2a87bee..866e929 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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 diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index e0c51fd..4986ecd 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -2,7 +2,7 @@ import Head from '@/components/Head.astro' --- - + diff --git a/src/pages/[lang]/about.astro b/src/pages/[lang]/about.astro new file mode 100644 index 0000000..e2b29d8 --- /dev/null +++ b/src/pages/[lang]/about.astro @@ -0,0 +1,7 @@ +--- +import Layout from '@/layouts/Layout.astro' +--- + + + about me + diff --git a/src/pages/[lang]/index.astro b/src/pages/[lang]/index.astro new file mode 100644 index 0000000..aa58087 --- /dev/null +++ b/src/pages/[lang]/index.astro @@ -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() +--- + + +
+ {pinnedPosts.length > 0 && ( +
+ +
+ )} + +
+ +
+
+
diff --git a/src/pages/[lang]/posts/[slug].astro b/src/pages/[lang]/posts/[slug].astro new file mode 100644 index 0000000..28928f3 --- /dev/null +++ b/src/pages/[lang]/posts/[slug].astro @@ -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() +--- + + +
+

{post.data.title}

+ + +
+
diff --git a/src/pages/[lang]/tags/[tags].astro b/src/pages/[lang]/tags/[tags].astro new file mode 100644 index 0000000..068dd92 --- /dev/null +++ b/src/pages/[lang]/tags/[tags].astro @@ -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() +--- + + +
+ {allTags.map(tag => ( + + {tag} + + ))} +
+ +
+ +
+
diff --git a/src/pages/[lang]/tags/index.astro b/src/pages/[lang]/tags/index.astro new file mode 100644 index 0000000..ab600e1 --- /dev/null +++ b/src/pages/[lang]/tags/index.astro @@ -0,0 +1,15 @@ +--- +import Layout from '@/layouts/Layout.astro' +import { getAllTags } from '@/utils/content.config' + +const allTags = await getAllTags() +--- + + +
+ {allTags.map(tag => ( + + {tag} + + ))} +
diff --git a/src/pages/about.astro b/src/pages/about.astro new file mode 100644 index 0000000..e2b29d8 --- /dev/null +++ b/src/pages/about.astro @@ -0,0 +1,7 @@ +--- +import Layout from '@/layouts/Layout.astro' +--- + + + about me + diff --git a/src/pages/index.astro b/src/pages/index.astro index 8357be6..a28ceb8 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -10,7 +10,6 @@ const pinnedPosts = await getPinnedPosts()
{pinnedPosts.length > 0 && (
-

置顶文章

    {pinnedPosts.map(post => (
  • @@ -25,7 +24,6 @@ const pinnedPosts = await getPinnedPosts() )}
    -

    所有文章

      {posts.map(post => (
    • diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index 8aaaa87..b6a1d0e 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -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} + ${locale} ${followConfig?.feedID && followConfig?.userID ? ` ${followConfig.feedID} diff --git a/src/pages/tags/[tags].astro b/src/pages/tags/[tags].astro new file mode 100644 index 0000000..068dd92 --- /dev/null +++ b/src/pages/tags/[tags].astro @@ -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() +--- + + +
      + {allTags.map(tag => ( + + {tag} + + ))} +
      + +
      + +
      +
      diff --git a/src/pages/tags/index.astro b/src/pages/tags/index.astro new file mode 100644 index 0000000..ab600e1 --- /dev/null +++ b/src/pages/tags/index.astro @@ -0,0 +1,15 @@ +--- +import Layout from '@/layouts/Layout.astro' +import { getAllTags } from '@/utils/content.config' + +const allTags = await getAllTags() +--- + + +
      + {allTags.map(tag => ( + + {tag} + + ))} +
      diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 2e25cbe..76d0198 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -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 diff --git a/src/utils/ui.ts b/src/utils/ui.ts index 7f0aef8..6800882 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -1,30 +1,44 @@ -export const language = { - zh: { +// Global Language Map +export const langMap: Record = { + '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: 'О себе',