Commit 29b9101
Changed files (42)
i18n
en
web
zh-CN
web
zh-TW
web
src
components
aside
search
widgets
i18n
pages
archives
posts
types
utils
i18n/en/web/index.ts
@@ -0,0 +1,79 @@
+import type { BaseTranslation } from '../../i18n-types.js';
+
+const en_web = {
+ common: {
+ open: 'Open',
+ close: 'Close',
+ },
+ navigation: {
+ home: 'Home',
+ about: 'About',
+ archive: {
+ title: 'Archive',
+ time: 'Time',
+ tags: 'Tags',
+ categories: 'Categories',
+ },
+ friendLinks: 'Links',
+ menu: 'Menu',
+ prevPage: 'Previous Page',
+ nextPage: 'Next Page',
+ recentPosts: 'Recent Posts',
+ },
+ status: {
+ totalPosts: 'Total Posts',
+ totalWords: 'Total Words',
+ lastUpdated: 'Last Updated',
+ runTime: 'Run Time',
+ wordsCount: '{0} word{{s}}',
+ readTime: '{0} minute{{s}}',
+ postsCount: '{0} post{{s}}',
+ tagsCount: '{0} tag{{s}}',
+ categoriesCount: '{0} category{{s}}',
+ },
+ button: {
+ search: 'Search',
+ subscribe: 'Subscribe',
+ more: 'More',
+ themeToggle: {
+ title: 'Toggle Theme',
+ lightMode: 'Light',
+ darkMode: 'Dark',
+ systemMode: 'System',
+ },
+ },
+ meta: {
+ author: 'Author',
+ publishedAt: 'Published At',
+ license: 'License',
+ tags: 'Tags',
+ category: 'Category',
+ unTagged: 'No Tags',
+ unCategorized: 'Uncategorized',
+ },
+ info: {
+ toc: 'Table of Content',
+ toolBar: 'Tool Bar',
+ readingPercentage: 'Reading Percentage',
+ comments: 'Comments',
+ recentComments: 'Recent Comments',
+ commentAbbrs: {
+ image: '[Image]',
+ link: '[Link]',
+ code: '[Code]',
+ },
+ backLinks: 'Back Links',
+ devNote:
+ 'This is a draft and will only be displayed in <code>DEV</code> mode. To disable draft preview, please modify <code>{configKey:string}</code> to <code>{configValue:boolean}</code> in <code>{configFilePath:string}</code>.',
+ openMenu: 'Open Menu',
+ closeMenu: 'Close Menu',
+ },
+ search: {
+ title: 'Search',
+ placeholder: 'Search Anything...',
+ searchResults: 'Search Results',
+ noSearchResults: 'No Results Found',
+ },
+} satisfies BaseTranslation;
+
+export default en_web;
i18n/zh-CN/web/index.ts
@@ -0,0 +1,79 @@
+import type { NamespaceWebTranslation } from '../../i18n-types.js';
+
+const zh_CN_web = {
+ common: {
+ open: '打开',
+ close: '关闭',
+ },
+ navigation: {
+ home: '首页',
+ about: '关于',
+ archive: {
+ title: '归档',
+ time: '时间',
+ tags: '标签',
+ categories: '分类',
+ },
+ friendLinks: '友链',
+ menu: '菜单',
+ prevPage: '上一页',
+ nextPage: '下一页',
+ recentPosts: '最近文章',
+ },
+ status: {
+ totalPosts: '文章总数',
+ totalWords: '字数总计',
+ lastUpdated: '最后更新',
+ runTime: '运行时间',
+ wordsCount: '{0} 字',
+ readTime: '{0} 分钟',
+ postsCount: '{0} 篇文章',
+ tagsCount: '{0} 个标签',
+ categoriesCount: '{0} 个分类',
+ },
+ button: {
+ search: '搜索',
+ subscribe: '订阅',
+ more: '更多',
+ themeToggle: {
+ title: '主题切换',
+ lightMode: '亮色',
+ darkMode: '暗色',
+ systemMode: '跟随系统',
+ },
+ },
+ meta: {
+ author: '作者',
+ publishedAt: '发布时间',
+ license: '许可协议',
+ tags: '标签',
+ category: '分类',
+ unTagged: '无标签',
+ unCategorized: '未分类',
+ },
+ info: {
+ toc: '目录',
+ toolBar: '工具栏',
+ readingPercentage: '阅读进度',
+ comments: '评论',
+ recentComments: '最新评论',
+ commentAbbrs: {
+ image: '[图片]',
+ link: '[链接]',
+ code: '[代码]',
+ },
+ backLinks: '反向链接',
+ devNote:
+ '这是一个草稿,只会在 <code>DEV</code> 模式下显示。要禁用草稿预览,请在 <code>{configFilePath}</code> 中将 <code>{configKey}</code> 修改为 <code>{configValue}</code>。',
+ openMenu: '打开菜单',
+ closeMenu: '关闭菜单',
+ },
+ search: {
+ title: '搜索',
+ placeholder: '搜索任何内容...',
+ searchResults: '搜索结果',
+ noSearchResults: '没有找到结果',
+ },
+} satisfies NamespaceWebTranslation;
+
+export default zh_CN_web;
i18n/zh-TW/web/index.ts
@@ -0,0 +1,79 @@
+import type { NamespaceWebTranslation } from '../../i18n-types.js';
+
+const zh_TW_web = {
+ common: {
+ open: '打開',
+ close: '關閉',
+ },
+ navigation: {
+ home: '首頁',
+ about: '關於',
+ archive: {
+ title: '歸檔',
+ time: '時間',
+ tags: '標籤',
+ categories: '分類',
+ },
+ friendLinks: '友鏈',
+ menu: '選單',
+ prevPage: '上一頁',
+ nextPage: '下一頁',
+ recentPosts: '最近文章',
+ },
+ status: {
+ totalPosts: '文章總數',
+ totalWords: '字數總計',
+ lastUpdated: '最後更新',
+ runTime: '運行時間',
+ wordsCount: '{0} 字',
+ readTime: '{0} 分鐘',
+ postsCount: '{0} 篇文章',
+ tagsCount: '{0} 個標籤',
+ categoriesCount: '{0} 個分類',
+ },
+ button: {
+ search: '搜尋',
+ subscribe: '訂閱',
+ more: '更多',
+ themeToggle: {
+ title: '主題切換',
+ lightMode: '亮色',
+ darkMode: '暗色',
+ systemMode: '跟隨系統',
+ },
+ },
+ meta: {
+ author: '作者',
+ publishedAt: '發佈時間',
+ license: '許可協議',
+ tags: '標籤',
+ category: '分類',
+ unTagged: '無標籤',
+ unCategorized: '未分類',
+ },
+ info: {
+ toc: '目錄',
+ toolBar: '工具欄',
+ readingPercentage: '閱讀進度',
+ comments: '評論',
+ recentComments: '最新評論',
+ commentAbbrs: {
+ image: '[圖片]',
+ link: '[連結]',
+ code: '[程式碼]',
+ },
+ backLinks: '反向連結',
+ devNote:
+ '這是一個草稿,只會在 <code>DEV</code> 模式下顯示。要禁用草稿預覽,請在 <code>{configFilePath}</code> 中將 <code>{configKey}</code> 修改為 <code>{configValue}</code>。',
+ openMenu: '打開選單',
+ closeMenu: '關閉選單',
+ },
+ search: {
+ title: '搜尋',
+ placeholder: '搜尋任何內容...',
+ searchResults: '搜尋結果',
+ noSearchResults: '沒有找到結果',
+ },
+} satisfies NamespaceWebTranslation;
+
+export default zh_TW_web;
i18n/custom-types.ts
@@ -0,0 +1,1 @@
+// use this file to export your custom types; these types will be imported by './i18n-types.ts'
\ No newline at end of file
i18n/i18n-types.ts
@@ -14,7 +14,8 @@ export type Translation = RootTranslation & DisallowNamespaces
export type Translations = RootTranslation &
{
- cli: NamespaceCliTranslation
+ cli: NamespaceCliTranslation,
+ web: NamespaceWebTranslation
}
type RootTranslation = {}
@@ -162,8 +163,248 @@ export type NamespaceCliTranslation = {
}
}
+export type NamespaceWebTranslation = {
+ common: {
+ /**
+ * Open
+ */
+ open: string
+ /**
+ * Close
+ */
+ close: string
+ }
+ navigation: {
+ /**
+ * Home
+ */
+ home: string
+ /**
+ * About
+ */
+ about: string
+ archive: {
+ /**
+ * Archive
+ */
+ title: string
+ /**
+ * Time
+ */
+ time: string
+ /**
+ * Tags
+ */
+ tags: string
+ /**
+ * Categories
+ */
+ categories: string
+ }
+ /**
+ * Links
+ */
+ friendLinks: string
+ /**
+ * Menu
+ */
+ menu: string
+ /**
+ * Previous Page
+ */
+ prevPage: string
+ /**
+ * Next Page
+ */
+ nextPage: string
+ /**
+ * Recent Posts
+ */
+ recentPosts: string
+ }
+ status: {
+ /**
+ * Total Posts
+ */
+ totalPosts: string
+ /**
+ * Total Words
+ */
+ totalWords: string
+ /**
+ * Last Updated
+ */
+ lastUpdated: string
+ /**
+ * Run Time
+ */
+ runTime: string
+ /**
+ * {0} word{{s}}
+ * @param {string | number | boolean} 0
+ */
+ wordsCount: RequiredParams<'0'>
+ /**
+ * {0} minute{{s}}
+ * @param {string | number | boolean} 0
+ */
+ readTime: RequiredParams<'0'>
+ /**
+ * {0} post{{s}}
+ * @param {string | number | boolean} 0
+ */
+ postsCount: RequiredParams<'0'>
+ /**
+ * {0} tag{{s}}
+ * @param {string | number | boolean} 0
+ */
+ tagsCount: RequiredParams<'0'>
+ /**
+ * {0} category{{s}}
+ * @param {string | number | boolean} 0
+ */
+ categoriesCount: RequiredParams<'0'>
+ }
+ button: {
+ /**
+ * Search
+ */
+ search: string
+ /**
+ * Subscribe
+ */
+ subscribe: string
+ /**
+ * More
+ */
+ more: string
+ themeToggle: {
+ /**
+ * Toggle Theme
+ */
+ title: string
+ /**
+ * Light
+ */
+ lightMode: string
+ /**
+ * Dark
+ */
+ darkMode: string
+ /**
+ * System
+ */
+ systemMode: string
+ }
+ }
+ meta: {
+ /**
+ * Author
+ */
+ author: string
+ /**
+ * Published At
+ */
+ publishedAt: string
+ /**
+ * License
+ */
+ license: string
+ /**
+ * Tags
+ */
+ tags: string
+ /**
+ * Category
+ */
+ category: string
+ /**
+ * No Tags
+ */
+ unTagged: string
+ /**
+ * Uncategorized
+ */
+ unCategorized: string
+ }
+ info: {
+ /**
+ * Table of Content
+ */
+ toc: string
+ /**
+ * Tool Bar
+ */
+ toolBar: string
+ /**
+ * Reading Percentage
+ */
+ readingPercentage: string
+ /**
+ * Comments
+ */
+ comments: string
+ /**
+ * Recent Comments
+ */
+ recentComments: string
+ commentAbbrs: {
+ /**
+ * [Image]
+ */
+ image: string
+ /**
+ * [Link]
+ */
+ link: string
+ /**
+ * [Code]
+ */
+ code: string
+ }
+ /**
+ * Back Links
+ */
+ backLinks: string
+ /**
+ * This is a draft and will only be displayed in <code>DEV</code> mode. To disable draft preview, please modify <code>{configKey}</code> to <code>{configValue}</code> in <code>{configFilePath}</code>.
+ * @param {string} configFilePath
+ * @param {string} configKey
+ * @param {boolean} configValue
+ */
+ devNote: RequiredParams<'configFilePath' | 'configKey' | 'configValue'>
+ /**
+ * Open Menu
+ */
+ openMenu: string
+ /**
+ * Close Menu
+ */
+ closeMenu: string
+ }
+ search: {
+ /**
+ * Search
+ */
+ title: string
+ /**
+ * Search Anything...
+ */
+ placeholder: string
+ /**
+ * Search Results
+ */
+ searchResults: string
+ /**
+ * No Results Found
+ */
+ noSearchResults: string
+ }
+}
+
export type Namespaces =
| 'cli'
+ | 'web'
type DisallowNamespaces = {
/**
@@ -171,6 +412,12 @@ type DisallowNamespaces = {
* you need to use the `./cli/index.ts` file instead
*/
cli?: "[typesafe-i18n] reserved for 'cli'-namespace. You need to use the `./cli/index.ts` file instead."
+
+ /**
+ * reserved for 'web'-namespace\
+ * you need to use the `./web/index.ts` file instead
+ */
+ web?: "[typesafe-i18n] reserved for 'web'-namespace. You need to use the `./web/index.ts` file instead."
}
export type TranslationFunctions = {
@@ -304,6 +551,236 @@ export type TranslationFunctions = {
rename_to_original_conflict: (arg: { fileName: string }) => LocalizedString
}
}
+ web: {
+ common: {
+ /**
+ * Open
+ */
+ open: () => LocalizedString
+ /**
+ * Close
+ */
+ close: () => LocalizedString
+ }
+ navigation: {
+ /**
+ * Home
+ */
+ home: () => LocalizedString
+ /**
+ * About
+ */
+ about: () => LocalizedString
+ archive: {
+ /**
+ * Archive
+ */
+ title: () => LocalizedString
+ /**
+ * Time
+ */
+ time: () => LocalizedString
+ /**
+ * Tags
+ */
+ tags: () => LocalizedString
+ /**
+ * Categories
+ */
+ categories: () => LocalizedString
+ }
+ /**
+ * Links
+ */
+ friendLinks: () => LocalizedString
+ /**
+ * Menu
+ */
+ menu: () => LocalizedString
+ /**
+ * Previous Page
+ */
+ prevPage: () => LocalizedString
+ /**
+ * Next Page
+ */
+ nextPage: () => LocalizedString
+ /**
+ * Recent Posts
+ */
+ recentPosts: () => LocalizedString
+ }
+ status: {
+ /**
+ * Total Posts
+ */
+ totalPosts: () => LocalizedString
+ /**
+ * Total Words
+ */
+ totalWords: () => LocalizedString
+ /**
+ * Last Updated
+ */
+ lastUpdated: () => LocalizedString
+ /**
+ * Run Time
+ */
+ runTime: () => LocalizedString
+ /**
+ * {0} word{{s}}
+ */
+ wordsCount: (arg0: string | number | boolean) => LocalizedString
+ /**
+ * {0} minute{{s}}
+ */
+ readTime: (arg0: string | number | boolean) => LocalizedString
+ /**
+ * {0} post{{s}}
+ */
+ postsCount: (arg0: string | number | boolean) => LocalizedString
+ /**
+ * {0} tag{{s}}
+ */
+ tagsCount: (arg0: string | number | boolean) => LocalizedString
+ /**
+ * {0} category{{s}}
+ */
+ categoriesCount: (arg0: string | number | boolean) => LocalizedString
+ }
+ button: {
+ /**
+ * Search
+ */
+ search: () => LocalizedString
+ /**
+ * Subscribe
+ */
+ subscribe: () => LocalizedString
+ /**
+ * More
+ */
+ more: () => LocalizedString
+ themeToggle: {
+ /**
+ * Toggle Theme
+ */
+ title: () => LocalizedString
+ /**
+ * Light
+ */
+ lightMode: () => LocalizedString
+ /**
+ * Dark
+ */
+ darkMode: () => LocalizedString
+ /**
+ * System
+ */
+ systemMode: () => LocalizedString
+ }
+ }
+ meta: {
+ /**
+ * Author
+ */
+ author: () => LocalizedString
+ /**
+ * Published At
+ */
+ publishedAt: () => LocalizedString
+ /**
+ * License
+ */
+ license: () => LocalizedString
+ /**
+ * Tags
+ */
+ tags: () => LocalizedString
+ /**
+ * Category
+ */
+ category: () => LocalizedString
+ /**
+ * No Tags
+ */
+ unTagged: () => LocalizedString
+ /**
+ * Uncategorized
+ */
+ unCategorized: () => LocalizedString
+ }
+ info: {
+ /**
+ * Table of Content
+ */
+ toc: () => LocalizedString
+ /**
+ * Tool Bar
+ */
+ toolBar: () => LocalizedString
+ /**
+ * Reading Percentage
+ */
+ readingPercentage: () => LocalizedString
+ /**
+ * Comments
+ */
+ comments: () => LocalizedString
+ /**
+ * Recent Comments
+ */
+ recentComments: () => LocalizedString
+ commentAbbrs: {
+ /**
+ * [Image]
+ */
+ image: () => LocalizedString
+ /**
+ * [Link]
+ */
+ link: () => LocalizedString
+ /**
+ * [Code]
+ */
+ code: () => LocalizedString
+ }
+ /**
+ * Back Links
+ */
+ backLinks: () => LocalizedString
+ /**
+ * This is a draft and will only be displayed in <code>DEV</code> mode. To disable draft preview, please modify <code>{configKey}</code> to <code>{configValue}</code> in <code>{configFilePath}</code>.
+ */
+ devNote: (arg: { configFilePath: string, configKey: string, configValue: boolean }) => LocalizedString
+ /**
+ * Open Menu
+ */
+ openMenu: () => LocalizedString
+ /**
+ * Close Menu
+ */
+ closeMenu: () => LocalizedString
+ }
+ search: {
+ /**
+ * Search
+ */
+ title: () => LocalizedString
+ /**
+ * Search Anything...
+ */
+ placeholder: () => LocalizedString
+ /**
+ * Search Results
+ */
+ searchResults: () => LocalizedString
+ /**
+ * No Results Found
+ */
+ noSearchResults: () => LocalizedString
+ }
+ }
}
export type Formatters = {}
i18n/i18n-util.async.ts
@@ -13,13 +13,16 @@ const localeTranslationLoaders = {
const localeNamespaceLoaders = {
en: {
- cli: () => import('./en/cli/index.js')
+ cli: () => import('./en/cli/index.js'),
+ web: () => import('./en/web/index.js')
},
'zh-CN': {
- cli: () => import('./zh-CN/cli/index.js')
+ cli: () => import('./zh-CN/cli/index.js'),
+ web: () => import('./zh-CN/web/index.js')
},
'zh-TW': {
- cli: () => import('./zh-TW/cli/index.js')
+ cli: () => import('./zh-TW/cli/index.js'),
+ web: () => import('./zh-TW/web/index.js')
}
}
i18n/i18n-util.sync.ts
@@ -10,21 +10,27 @@ import zh_CN from './zh-CN/index.js'
import zh_TW from './zh-TW/index.js'
import en_cli from './en/cli/index.js'
+import en_web from './en/web/index.js'
import zh_CN_cli from './zh-CN/cli/index.js'
+import zh_CN_web from './zh-CN/web/index.js'
import zh_TW_cli from './zh-TW/cli/index.js'
+import zh_TW_web from './zh-TW/web/index.js'
const localeTranslations = {
en: {
...en,
- cli: en_cli
+ cli: en_cli,
+ web: en_web
},
'zh-CN': {
...zh_CN,
- cli: zh_CN_cli
+ cli: zh_CN_cli,
+ web: zh_CN_web
},
'zh-TW': {
...zh_TW,
- cli: zh_TW_cli
+ cli: zh_TW_cli,
+ web: zh_TW_web
},
}
i18n/i18n-util.ts
@@ -17,7 +17,8 @@ export const locales: Locales[] = [
]
export const namespaces: Namespaces[] = [
- 'cli'
+ 'cli',
+ 'web'
]
export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales)
src/components/aside/recent-comments/utils.ts
@@ -1,13 +1,12 @@
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
export function cleanCommentHtml(htmlString: string) {
return htmlString
- .replaceAll(/<img.*?src="(.*?)"?[^>]+>/gi, i18n(I18nKey.commentReplaceImage)!)
+ .replaceAll(/<img.*?src="(.*?)"?[^>]+>/gi, t.info.commentAbbrs.image())
.replaceAll(
/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,
- i18n(I18nKey.commentReplaceLink)!
+ t.info.commentAbbrs.link()
)
- .replaceAll(/<pre><code[^>]+?>.*?<\/pre>/gis, i18n(I18nKey.commentReplaceCode)!)
+ .replaceAll(/<pre><code[^>]+?>.*?<\/pre>/gis, t.info.commentAbbrs.code())
.replaceAll(/<[^>]+>/g, '');
}
src/components/aside/siteinfo/Stats.astro
@@ -1,8 +1,7 @@
---
import { asideConfig, siteConfig } from '@/config';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import { getPosts, getPostsCount } from '@utils/content-utils';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import { render } from 'astro:content';
---
@@ -12,14 +11,14 @@ import { render } from 'astro:content';
asideConfig.siteInfo.stats.map(async (entry) => {
switch (entry) {
case 'post-count': {
- const postCount = await getPostsCount();
+ const postsCount = await getPostsCount();
return (
<div class="stat">
<div class="stat-title flex flex-row items-center gap-1">
<Icon name="material-symbols:folder-open-rounded" />
- <span>{i18n(I18nKey.totalPosts)}</span>
+ <span>{t.status.totalPosts}</span>
</div>
- <div class="stat-value text-base">{`${postCount} ${postCount > 1 ? i18n(I18nKey.postsCount) : i18n(I18nKey.postCount)}`}</div>
+ <div class="stat-value text-base">{t.status.postsCount(postsCount)}</div>
</div>
);
}
@@ -28,7 +27,7 @@ import { render } from 'astro:content';
<div class="stat">
<div class="stat-title flex flex-row items-center gap-1">
<Icon name="material-symbols:refresh-rounded" />
- <span>{i18n(I18nKey.lastUpdated)}</span>
+ <span>{t.status.lastUpdated()}</span>
</div>
<div class="stat-value text-base">
<time datetime={new Date().toISOString()}>
@@ -42,7 +41,7 @@ import { render } from 'astro:content';
<div class="stat">
<div class="stat-title flex flex-row items-center gap-1">
<Icon name="material-symbols:docs-rounded" />
- <span>{i18n(I18nKey.totalWords)}</span>
+ <span>{t.status.totalWords()}</span>
</div>
<div class="stat-value text-base">
{(async () => {
@@ -54,7 +53,7 @@ import { render } from 'astro:content';
})
);
const total = words.reduce((acc, cur) => acc + cur, 0);
- return `${total} ${total > 1 ? i18n(I18nKey.wordsCount) : i18n(I18nKey.wordCount)}`;
+ return t.status.wordsCount(total);
})()}
</div>
</div>
@@ -64,7 +63,7 @@ import { render } from 'astro:content';
<div class="stat">
<div class="stat-title flex flex-row items-center gap-1">
<Icon name="material-symbols:calendar-clock-rounded" />
- <span>{i18n(I18nKey.runTime)}</span>
+ <span>{t.status.runTime()}</span>
</div>
<div class="stat-value text-base">
<time
src/components/aside/siteinfo/Tags.astro
@@ -1,8 +1,7 @@
---
import Button from '@components/widgets/Button.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import { getTags, getTagUrl } from '@utils/content-utils';
+import { t } from '@utils/i18n';
const tagsMap = await getTags();
---
@@ -20,7 +19,7 @@ const tagsMap = await getTags();
}
</div>
<div class="btn bg-base-200/50 btn-wide hidden rounded-sm hover:brightness-125">
- {i18n(I18nKey.more)}
+ {t.button.more()}
</div>
</div>
src/components/aside/RecentCommentsCard.vue
@@ -4,8 +4,7 @@ import { TwikooProvider } from './recent-comments/Twikoo';
import { WalineProvider } from './recent-comments/Waline';
import type { CommentData } from './recent-comments/types';
import { asideConfig, commentConfig, siteConfig } from '@/config';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { onMounted, ref } from 'vue';
const comments = ref<CommentData[]>([]);
@@ -47,7 +46,7 @@ onMounted(() => {
<div id="recent-comments-card" class="card border-base-300 bg-base-200/25 border">
<div class="card-body px-4 py-2">
<div class="card-title">
- {{ i18n(I18nKey.recentComments) }}
+ {{ t.info.recentComments() }}
</div>
<ul class="list">
<template v-if="!loading">
src/components/misc/BackLinks.astro
@@ -1,6 +1,5 @@
---
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
interface Props {
backLinks: {
@@ -20,7 +19,7 @@ const { backLinks } = Astro.props;
<div class="bg-base-100 border-base-content/25 collapse-plus collapse my-4 border">
<input type="checkbox" />
- <div class="collapse-title text-lg font-semibold">{i18n(I18nKey.backLinks)}</div>
+ <div class="collapse-title text-lg font-semibold">{t.info.backLinks()}</div>
<div class="collapse-content">
<ul class="list">
{
src/components/misc/CategoryBar.astro
@@ -1,7 +1,6 @@
---
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import { getCategoryUrl } from '@utils/content-utils';
+import { t } from '@utils/i18n';
interface Props {
categories: string[];
@@ -17,8 +16,8 @@ const { categories, currentCategory } = Astro.props;
>
<nav
class="card-body flex flex-row items-center gap-2 overflow-auto px-2 py-3"
- title={i18n(I18nKey.categories)}
- aria-label={i18n(I18nKey.categories)}
+ title={t.navigation.archive.categories()}
+ aria-label={t.navigation.archive.categories()}
role="navigation"
>
<a
@@ -28,7 +27,7 @@ const { categories, currentCategory } = Astro.props;
currentCategory ? '' : 'btn-active',
]}
>
- {i18n(I18nKey.recentPosts)}
+ {t.navigation.recentPosts()}
</a>
{
categories.map((category) => (
@@ -39,7 +38,7 @@ const { categories, currentCategory } = Astro.props;
currentCategory === category ? 'btn-active' : '',
]}
>
- {i18n(category)}
+ {category}
</a>
))
}
src/components/misc/License.astro
@@ -1,7 +1,6 @@
---
import { licenseConfig, profileConfig, siteConfig } from '@/config';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
interface Props {
time: Date;
@@ -14,17 +13,17 @@ const { time, license, licenseUrl, lang } = Astro.props;
const infomations = [
{
- key: i18n(I18nKey.publishedAt),
+ key: t.meta.publishedAt(),
value: time.toLocaleDateString(lang || siteConfig.lang.replace('_', '-')),
time: time,
},
{
- key: i18n(I18nKey.author),
+ key: t.meta.author(),
value: profileConfig.name,
link: '/about/',
},
{
- key: i18n(I18nKey.license),
+ key: t.meta.license(),
value: license || licenseConfig.name,
link: licenseUrl || licenseConfig.url,
},
src/components/misc/PostInfo.astro
@@ -1,9 +1,8 @@
---
import { articleConfig, siteConfig } from '@/config';
import MetaIcon from '@components/widgets/MetaIcon.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import { getCategoryUrl, getTagUrl } from '@utils/content-utils';
+import { t } from '@utils/i18n';
interface Props {
title: string;
@@ -36,13 +35,13 @@ const metas: ({ icon: string; text: string; link?: string; time?: Date } | undef
articleConfig.wordCount && typeof wordCount === 'number'
? {
icon: 'material-symbols:docs-rounded',
- text: `${wordCount} ${wordCount === 1 ? i18n(I18nKey.wordCount) : i18n(I18nKey.wordsCount)}`,
+ text: t.status.wordsCount(wordCount),
}
: undefined,
articleConfig.readingTime && typeof readingTime === 'number'
? {
icon: 'material-symbols:nest-clock-farsight-analog-rounded',
- text: `${readingTime} ${readingTime === 1 ? i18n(I18nKey.minuteCount) : i18n(I18nKey.minutesCount)}`,
+ text: t.status.readTime(readingTime),
}
: undefined,
category
src/components/search/Pagefind.vue
@@ -3,8 +3,7 @@ import type {
PagefindSearchResult,
PagefindSearchResults,
} from '@/types/PagefindSearchAPI.d.ts';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { type Ref, onMounted, ref } from 'vue';
const isLoading = ref(false);
@@ -109,7 +108,7 @@ defineExpose({
<slot></slot>
<div
class="search-result mt-4 flex h-fit max-h-[calc(60vh-8rem)] flex-col items-center gap-2 overflow-y-auto text-center"
- :aria-label="i18n(I18nKey.searchResults)"
+ :aria-label="t.search.searchResults()"
tabindex="-1"
>
<template v-if="isLoading">
@@ -121,7 +120,7 @@ defineExpose({
<div class="skeleton mt-1 h-4 w-3/4"></div>
</div>
</template>
- <template v-else-if="noResults">{{ i18n(I18nKey.noSearchResults) }}</template>
+ <template v-else-if="noResults">{{ t.search.noSearchResults() }}</template>
<template v-else>
<a
v-for="result in searchResults"
src/components/widgets/SideToolBar/TocButton.vue
@@ -1,6 +1,5 @@
<script setup lang="ts">
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { onMounted, onUnmounted, ref } from 'vue';
const isOpen = ref(false);
@@ -65,8 +64,8 @@ onUnmounted(() => {
ref="buttonRef"
class="btn btn-circle btn-secondary btn-sm"
@click="isOpen = !isOpen"
- :title="i18n(I18nKey.toc)"
- :aria-label="i18n(I18nKey.toc)"
+ :title="t.info.toc()"
+ :aria-label="t.info.toc()"
:aria-expanded="isOpen"
:aria-controls="'stb-toc-wrapper'"
>
src/components/widgets/DarkModeButton.astro
@@ -1,6 +1,5 @@
---
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import type { HTMLAttributes } from 'astro/types';
import Button from './Button.astro';
@@ -17,11 +16,11 @@ const { class: className, showText, useDefaultBtnClass, ...rest } = Astro.props;
class:list={['darkmode-btn swap swap-rotate', className]}
useDefaultClass={useDefaultBtnClass}
{...rest}
- data-text-light={i18n(I18nKey.lightMode)}
- data-text-dark={i18n(I18nKey.darkMode)}
- data-text-auto={i18n(I18nKey.systemMode)}
+ data-text-light={t.button.themeToggle.lightMode()}
+ data-text-dark={t.button.themeToggle.darkMode()}
+ data-text-auto={t.button.themeToggle.systemMode()}
role="switch"
- aria-label={i18n(I18nKey.themeToggle)}
+ aria-label={t.button.themeToggle.title()}
>
<input type="checkbox" inert />
<Icon
src/components/widgets/Pagination.astro
@@ -1,6 +1,5 @@
---
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import Button from './Button.astro';
@@ -55,12 +54,12 @@ else {
current < total ? 'max-xs:w-[calc(50%-1rem)]' : 'max-xs:w-[calc(100%-1rem)]',
]}
href={getPageUrl(current - 1)}
- title={i18n(I18nKey.prevPage)}
- aria-label={i18n(I18nKey.prevPage)}
+ title={t.navigation.prevPage()}
+ aria-label={t.navigation.prevPage()}
rel="prev"
>
<Icon name="material-symbols:chevron-left-rounded" class="my-1 text-2xl" />
- <span class="xs:hidden">{i18n(I18nKey.prevPage)}</span>
+ <span class="xs:hidden">{t.navigation.prevPage()}</span>
</Button>
)
}
@@ -118,11 +117,11 @@ else {
current > 1 ? 'max-xs:w-[calc(50%-1rem)]' : 'max-xs:w-[calc(100%-1rem)]',
]}
href={getPageUrl(current + 1)}
- title={i18n(I18nKey.nextPage)}
- aria-label={i18n(I18nKey.nextPage)}
+ title={t.navigation.nextPage()}
+ aria-label={t.navigation.nextPage()}
rel="next"
>
- <span class="xs:hidden">{i18n(I18nKey.nextPage)}</span>
+ <span class="xs:hidden">{t.navigation.nextPage()}</span>
<Icon name="material-symbols:chevron-right-rounded" class="my-1 text-2xl" />
</Button>
)
src/components/widgets/ReadMoreButton.astro
@@ -1,6 +1,5 @@
---
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import type { ComponentProps } from 'astro/types';
@@ -11,7 +10,7 @@ const { href, title, ...rest } = Astro.props;
<a
href={href}
- title={title || i18n(I18nKey.more)}
+ title={title || t.button.more()}
class="duration-150 hover:brightness-125 active:brightness-75 max-md:hidden"
>
<Icon
src/components/Comment.astro
@@ -4,11 +4,10 @@ import Artalk from '@components/comment/Artalk.astro';
import Giscus from '@components/comment/Giscus.astro';
import Twikoo from '@components/comment/Twikoo.astro';
import Waline from '@components/comment/Waline.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
---
-<div id="page-comment" title={i18n(I18nKey.comments)} aria-label={i18n(I18nKey.comments)}>
+<div id="page-comment" title={t.info.comments()} aria-label={t.info.comments()}>
{
(() => {
switch (commentConfig.provider) {
src/components/Search.astro
@@ -1,7 +1,6 @@
---
import { searchConfig } from '@/config';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import Pagefind from './search/Pagefind.vue';
---
@@ -11,8 +10,8 @@ import Pagefind from './search/Pagefind.vue';
<form method="dialog">
<button
class="btn btn-circle btn-ghost btn-sm absolute top-2 right-2"
- title={i18n(I18nKey.close)}
- aria-label={i18n(I18nKey.close)}>✕</button
+ title={t.common.close()}
+ aria-label={t.common.close()}>✕</button
>
</form>
<div class="w-full p-4">
@@ -31,7 +30,7 @@ import Pagefind from './search/Pagefind.vue';
autocomplete="off"
autocapitalize="off"
class="grow"
- placeholder={i18n(I18nKey.search)}
+ placeholder={t.button.search()}
/>
<Icon
name="material-symbols:search-rounded"
@@ -68,6 +67,6 @@ import Pagefind from './search/Pagefind.vue';
</div>
</div>
<form method="dialog" class="modal-backdrop">
- <button>{i18n(I18nKey.close)}</button>
+ <button>{t.common.close()}</button>
</form>
</dialog>
src/components/SideToolBar.astro
@@ -1,7 +1,6 @@
---
import { articleConfig, toolBarConfig } from '@/config';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import Button from './widgets/Button.astro';
import DarkModeButton from './widgets/DarkModeButton.astro';
@@ -11,7 +10,7 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
<div
id="side-toolbar"
class="fixed right-0 bottom-10 z-30 grid grid-cols-1 gap-2"
- aria-label={i18n(I18nKey.toolBar)}
+ aria-label={t.info.toolBar()}
>
<div
id="stb-hide"
@@ -62,7 +61,7 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
id="stb-show-more"
class="btn-circle btn-secondary btn-sm"
aria-expanded="false"
- aria-label={i18n(I18nKey.more)}
+ aria-label={t.button.more()}
aria-controls="stb-hide"
>
<Icon name="material-symbols:settings-rounded" class="animate-spin" />
@@ -77,7 +76,7 @@ import TocButton from './widgets/SideToolBar/TocButton.vue';
<Button id="stb-back-to-top" class="group btn-circle btn-secondary btn-sm">
<span
id="stb-read-percentage"
- aria-label={i18n(I18nKey.toolBarReadingPercentage)}
+ aria-label={t.info.readingPercentage()}
class="absolute text-sm opacity-0 duration-300 group-hover:opacity-0">0</span
>
<Icon
src/i18n/langs/en.ts
@@ -1,73 +0,0 @@
-import Key from '../I18nKey';
-import type { Translation } from '../translation';
-
-export const en: Translation = {
- [Key.home]: 'Home',
- [Key.about]: 'About',
- [Key.archive]: 'Archive',
- [Key.search]: 'Search',
- [Key.links]: 'Links',
- [Key.time]: 'Time',
- [Key.menu]: 'Menu',
- [Key.close]: 'Close',
- [Key.open]: 'Open',
-
- [Key.prevPage]: 'Previous Page',
- [Key.nextPage]: 'Next Page',
-
- [Key.tags]: 'Tags',
- [Key.categories]: 'Categories',
- [Key.recentPosts]: 'Recent Posts',
- [Key.randomPost]: 'Random Post',
-
- [Key.comments]: 'Comments',
- [Key.recentComments]: 'Recent Comments',
- [Key.subscribe]: 'Subscribe',
- [Key.backLinks]: 'Back Links',
-
- [Key.untitled]: 'Untitled',
- [Key.uncategorized]: 'Uncategorized',
- [Key.untagged]: 'No Tags',
-
- [Key.totalPosts]: 'Total Posts',
- [Key.totalWords]: 'Total Words',
- [Key.lastUpdated]: 'Last Updated',
- [Key.runTime]: 'Run Time',
-
- [Key.wordCount]: 'word',
- [Key.wordsCount]: 'words',
- [Key.minuteCount]: 'minute',
- [Key.minutesCount]: 'minutes',
- [Key.postCount]: 'post',
- [Key.postsCount]: 'posts',
- [Key.tagCount]: 'tag',
- [Key.tagsCount]: 'tags',
- [Key.categoryCount]: 'category',
- [Key.categoriesCount]: 'categories',
-
- [Key.searchResults]: 'Search Results',
- [Key.noSearchResults]: 'No Results Found',
-
- [Key.toc]: 'Table of Content',
-
- [Key.toolBar]: 'Tool Bar',
- [Key.toolBarReadingPercentage]: 'Reading Percentage',
-
- [Key.themeToggle]: 'Toggle Theme',
- [Key.lightMode]: 'Light',
- [Key.darkMode]: 'Dark',
- [Key.systemMode]: 'System',
-
- [Key.more]: 'More',
-
- [Key.author]: 'Author',
- [Key.publishedAt]: 'Published at',
- [Key.license]: 'License',
-
- [Key.commentReplaceLink]: '[Link]',
- [Key.commentReplaceImage]: '[Image]',
- [Key.commentReplaceCode]: '[Code]',
-
- [Key.draftDevNote]:
- 'This is a draft and will only be displayed in `DEV` mode. To disable draft preview, please modify `buildConfig.showDraftsOnDev` to `false` in `src/config.ts`.',
-};
src/i18n/langs/zh_CN.ts
@@ -1,73 +0,0 @@
-import Key from '../I18nKey';
-import type { Translation } from '../translation';
-
-export const zh_CN: Translation = {
- [Key.home]: '主页',
- [Key.about]: '关于',
- [Key.archive]: '归档',
- [Key.search]: '搜索',
- [Key.links]: '友链',
- [Key.time]: '时间',
- [Key.menu]: '菜单',
- [Key.close]: '关闭',
- [Key.open]: '打开',
-
- [Key.prevPage]: '上一页',
- [Key.nextPage]: '下一页',
-
- [Key.tags]: '标签',
- [Key.categories]: '分类',
- [Key.recentPosts]: '最新文章',
- [Key.randomPost]: '随机文章',
-
- [Key.comments]: '评论',
- [Key.recentComments]: '最新评论',
- [Key.subscribe]: '订阅',
- [Key.backLinks]: '反向链接',
-
- [Key.untitled]: '无标题',
- [Key.uncategorized]: '未分类',
- [Key.untagged]: '无标签',
-
- [Key.totalPosts]: '文章总数',
- [Key.totalWords]: '字数总计',
- [Key.lastUpdated]: '最后更新',
- [Key.runTime]: '运行时间',
-
- [Key.wordCount]: '字',
- [Key.wordsCount]: '字',
- [Key.minuteCount]: '分钟',
- [Key.minutesCount]: '分钟',
- [Key.postCount]: '篇文章',
- [Key.postsCount]: '篇文章',
- [Key.tagCount]: '个标签',
- [Key.tagsCount]: '个标签',
- [Key.categoryCount]: '个分类',
- [Key.categoriesCount]: '个分类',
-
- [Key.searchResults]: '搜索结果',
- [Key.noSearchResults]: '没有找到结果',
-
- [Key.toc]: '目录',
-
- [Key.toolBar]: '工具栏',
- [Key.toolBarReadingPercentage]: '阅读进度',
-
- [Key.themeToggle]: '主题切换',
- [Key.lightMode]: '亮色',
- [Key.darkMode]: '暗色',
- [Key.systemMode]: '跟随系统',
-
- [Key.more]: '更多',
-
- [Key.author]: '作者',
- [Key.publishedAt]: '发布于',
- [Key.license]: '许可协议',
-
- [Key.commentReplaceLink]: '[链接]',
- [Key.commentReplaceImage]: '[图片]',
- [Key.commentReplaceCode]: '[代码]',
-
- [Key.draftDevNote]:
- '这是一篇草稿,只会在 `DEV` 模式下显示。关闭草稿预览,请修改 `src/config.ts` 中的 `buildConfig.showDraftsOnDev` 为 `false`。',
-};
src/i18n/langs/zh_TW.ts
@@ -1,73 +0,0 @@
-import Key from '../I18nKey';
-import type { Translation } from '../translation';
-
-export const zh_TW: Translation = {
- [Key.home]: '首頁',
- [Key.about]: '關於',
- [Key.archive]: '彙整',
- [Key.search]: '搜尋',
- [Key.links]: '連結',
- [Key.time]: '時間',
- [Key.menu]: '選單',
- [Key.close]: '關閉',
- [Key.open]: '開啟',
-
- [Key.prevPage]: '上一頁',
- [Key.nextPage]: '下一頁',
-
- [Key.tags]: '標籤',
- [Key.categories]: '分類',
- [Key.recentPosts]: '最新文章',
- [Key.randomPost]: '隨機文章',
-
- [Key.comments]: '評論',
- [Key.recentComments]: '最新評論',
- [Key.subscribe]: '訂閱',
- [Key.backLinks]: '反向連結',
-
- [Key.untitled]: '無標題',
- [Key.uncategorized]: '未分類',
- [Key.untagged]: '無標籤',
-
- [Key.totalPosts]: '文章總數',
- [Key.totalWords]: '字數總計',
- [Key.lastUpdated]: '最後更新',
- [Key.runTime]: '運行時間',
-
- [Key.wordCount]: '字',
- [Key.wordsCount]: '字',
- [Key.minuteCount]: '分鐘',
- [Key.minutesCount]: '分鐘',
- [Key.postCount]: '篇文章',
- [Key.postsCount]: '篇文章',
- [Key.tagCount]: '個標籤',
- [Key.tagsCount]: '個標籤',
- [Key.categoryCount]: '個分類',
- [Key.categoriesCount]: '個分類',
-
- [Key.searchResults]: '搜尋結果',
- [Key.noSearchResults]: '沒有找到結果',
-
- [Key.toc]: '目錄',
-
- [Key.toolBar]: '工具列',
- [Key.toolBarReadingPercentage]: '閱讀進度',
-
- [Key.themeToggle]: '主題切換',
- [Key.lightMode]: '亮色',
- [Key.darkMode]: '暗色',
- [Key.systemMode]: '跟隨系統',
-
- [Key.more]: '更多',
-
- [Key.author]: '作者',
- [Key.publishedAt]: '發佈於',
- [Key.license]: '許可協議',
-
- [Key.commentReplaceLink]: '[連結]',
- [Key.commentReplaceImage]: '[圖片]',
- [Key.commentReplaceCode]: '[程式碼]',
-
- [Key.draftDevNote]:
- '這是一篇草稿,只會在 `DEV` 模式下顯示。關閉草稿預覽,請修改 `src/config.ts` 中的 `buildConfig.showDraftsOnDev` 為 `false`。',
-};
src/i18n/I18nKey.ts
@@ -1,73 +0,0 @@
-enum I18nKey {
- home = 'home',
- about = 'about',
- archive = 'archive',
- search = 'search',
- links = 'links',
- time = 'time',
- menu = 'menu',
- close = 'close',
- open = 'open',
-
- prevPage = 'prevPage',
- nextPage = 'nextPage',
-
- tags = 'tags',
- categories = 'categories',
- recentPosts = 'recentPosts',
- randomPost = 'randomPost',
-
- comments = 'comments',
- recentComments = 'recentComments',
- subscribe = 'subscribe',
- backLinks = 'backLinks',
-
- untitled = 'untitled',
- uncategorized = 'uncategorized',
- untagged = 'untagged',
-
- totalPosts = 'totalPosts',
- totalWords = 'totalWords',
- lastUpdated = 'lastUpdated',
- runTime = 'runTime',
-
- wordCount = 'wordCount',
- wordsCount = 'wordsCount',
- minuteCount = 'minuteCount',
- minutesCount = 'minutesCount',
- postCount = 'postCount',
- postsCount = 'postsCount',
- tagCount = 'tagCount',
- tagsCount = 'tagsCount',
- categoryCount = 'categoryCount',
- categoriesCount = 'categoriesCount',
-
- searchResults = 'searchResults',
- noSearchResults = 'noSearchResults',
-
- toc = 'toc',
-
- toolBar = 'toolBar',
- toolBarReadingPercentage = 'toolBarReadingPercentage',
-
- themeToggle = 'themeToggle',
- lightMode = 'lightMode',
- darkMode = 'darkMode',
- systemMode = 'systemMode',
-
- more = 'more',
-
- author = 'author',
- publishedAt = 'publishedAt',
- license = 'license',
-
- /** The replace text for the comment content, used in Recent Comments. */
- commentReplaceLink = 'commentReplaceLink',
- commentReplaceImage = 'commentReplaceImage',
- commentReplaceCode = 'commentReplaceCode',
-
- /** Note in the top of drafts content in dev mode. This key supports markdown syntax, using `markdown-it`. */
- draftDevNote = 'draftDevNote',
-}
-
-export default I18nKey;
src/i18n/translation.ts
@@ -1,31 +0,0 @@
-import I18nKey from './I18nKey';
-import { en } from './langs/en';
-import { zh_CN } from './langs/zh_CN';
-import { zh_TW } from './langs/zh_TW';
-import { siteConfig } from '@/config';
-
-export type Translation = {
- [K in I18nKey]: string;
-};
-
-const defaultTranslation = en;
-
-const map: { [key: string]: Translation } = {
- en: en,
- en_us: en,
- en_gb: en,
- en_au: en,
- zh_cn: zh_CN,
- zh_tw: zh_TW,
-};
-
-export function getTranslation(lang: string): Translation {
- return map[lang.toLowerCase()] || defaultTranslation;
-}
-
-export function i18n(key: I18nKey | string | undefined): string | undefined {
- if (typeof key === 'undefined') return undefined;
- const lang = siteConfig.lang || 'en';
- const translate = getTranslation(lang);
- return key in I18nKey ? translate[key as I18nKey] : key;
-}
src/pages/archives/categories/[category]/[page].astro
@@ -5,14 +5,16 @@ import RecentCommentsCard from '@components/aside/RecentCommentsCard.vue';
import SiteInfoCard from '@components/aside/SiteInfoCard.astro';
import CategoryBar from '@components/misc/CategoryBar.astro';
import PostsPage from '@components/PostsPage.astro';
-import I18nKey from '@i18n/I18nKey';
import MainLayout from '@layouts/MainLayout.astro';
import { getCategories, getSortedPosts } from '@utils/content-utils';
+import { t } from '@utils/i18n';
export async function getStaticPaths() {
const posts = await getSortedPosts();
const categories = [
- ...new Set(posts.map((post) => post.data.category || I18nKey.uncategorized)),
+ ...new Set(
+ posts.map((post) => post.data.category || t?.meta.unCategorized() || 'uncategorized')
+ ),
];
return categories
.map((category) => {
src/pages/archives/categories/index.astro
@@ -10,9 +10,8 @@ import ProfileCard from '@components/aside/ProfileCard.astro';
import TOC from '@components/aside/TOC.astro';
import Button from '@components/widgets/Button.astro';
import MetaIcon from '@components/widgets/MetaIcon.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import MainLayout from '@layouts/MainLayout.astro';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
const categoriesMap = await getCategories();
@@ -25,29 +24,21 @@ for (const [category] of categoriesMap) {
}
const uncategorizedPosts = allPosts.filter((post) => !post.data.category).slice(0, 3);
if (uncategorizedPosts.length > 0)
- categoryPosts.set(i18n(I18nKey.uncategorized) as string, uncategorizedPosts);
+ categoryPosts.set(t.meta.unCategorized(), uncategorizedPosts);
---
-<MainLayout title={i18n(I18nKey.categories)}>
+<MainLayout title={t.navigation.archive.categories()}>
<div
class="card bg-base-200/25 border-base-300 swup-transition-fade mx-auto flex flex-col items-center border px-6 py-4"
>
<div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
- <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.categories)}</h1>
+ <h1 class="text-center text-3xl font-bold">{t.navigation.archive.categories()}</h1>
<div class="tooltip-content flex flex-col">
<div>
- {
- `${allPosts.length} ${i18n(
- allPosts.length > 1 ? I18nKey.postsCount : I18nKey.postCount
- )}, `
- }
+ {t.status.postsCount(allPosts.length) + ', '}
</div>
<div>
- {
- `${categoriesMap.size} ${i18n(
- categoriesMap.size > 1 ? I18nKey.categoriesCount : I18nKey.categoryCount
- )}`
- }
+ {t.status.categoriesCount(categoriesMap.size)}
</div>
</div>
</div>
@@ -79,7 +70,7 @@ if (uncategorizedPosts.length > 0)
</span>
</h2>
<Button href={getCategoryUrl(category)} title={category} class="pl-3">
- {i18n(I18nKey.more)}
+ {t.button.more()}
<Icon
name="material-symbols:chevron-right-rounded"
height="1.5rem"
@@ -102,7 +93,7 @@ if (uncategorizedPosts.length > 0)
{[
{
icon: 'material-symbols:category-outline-rounded',
- text: data.category || i18n(I18nKey.uncategorized),
+ text: data.category || t.meta.unCategorized(),
link: getCategoryUrl(data.category),
},
...(data.tags?.map((tag) => {
src/pages/archives/[...time].astro
@@ -4,10 +4,9 @@ import type { BlogPostData } from '@/types/data';
import ProfileCard from '@components/aside/ProfileCard.astro';
import TOC from '@components/aside/TOC.astro';
import MetaIcon from '@components/widgets/MetaIcon.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import MainLayout from '@layouts/MainLayout.astro';
import { getCategoryUrl, getPostsCount, getSortedPosts, getTagUrl } from '@utils/content-utils';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import dayjs from 'dayjs';
@@ -66,9 +65,9 @@ const postCount = await getPostsCount();
<MainLayout>
<div class="card border-base-300 bg-base-200/25 swup-transition-fade border px-6 py-4">
<div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
- <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.archive)}</h1>
+ <h1 class="text-center text-3xl font-bold">{t.navigation.archive.title()}</h1>
<div class="tooltip-content">
- {`${postCount} ${i18n(postCount > 1 ? I18nKey.postsCount : I18nKey.postCount)}`}
+ {t.status.postsCount(postCount)}
</div>
</div>
{
@@ -97,7 +96,7 @@ const postCount = await getPostsCount();
{[
{
icon: 'material-symbols:category-outline-rounded',
- text: data.category || i18n(I18nKey.uncategorized),
+ text: data.category || t.meta.unCategorized(),
link: getCategoryUrl(data.category),
},
...data.tags.map((tag) => {
src/pages/posts/[article].astro
@@ -5,13 +5,11 @@ import License from '@components/misc/License.astro';
import PostInfo from '@components/misc/PostInfo.astro';
import ImageWrapper from '@components/utils/ImageWrapper.astro';
import Markdown from '@components/utils/Markdown.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import PostPageLayout from '@layouts/PostPageLayout.astro';
import { getAllReferences, getPosts } from '@utils/content-utils';
+import { t } from '@utils/i18n';
import { Icon } from 'astro-icon/components';
import { render } from 'astro:content';
-import MarkdownIt from 'markdown-it';
export async function getStaticPaths() {
const posts = await getPosts();
@@ -88,7 +86,13 @@ const backLinks: {
<Icon name="material-symbols:info-outline-rounded" />
NOTE
</p>
- <Fragment set:html={new MarkdownIt().render(i18n(I18nKey.draftDevNote)!)} />
+ <Fragment
+ set:html={t.info.devNote({
+ configKey: 'buildConfig.showDraftsOnDev',
+ configValue: false,
+ configFilePath: 'src/config.ts',
+ })}
+ />
</div>
)
}
src/pages/about.astro
@@ -1,10 +1,9 @@
---
import PostInfo from '@components/misc/PostInfo.astro';
import Markdown from '@components/utils/Markdown.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import PostPageLayout from '@layouts/PostPageLayout.astro';
import { getAllReferences } from '@utils/content-utils';
+import { t } from '@utils/i18n';
import { getEntry, render } from 'astro:content';
const md = await getEntry('spec', 'about');
@@ -31,12 +30,12 @@ if (md) {
---
<PostPageLayout
- title={md?.data.title || (i18n(I18nKey.about) as string)}
+ title={md?.data.title || t.navigation.about()}
headings={headings}
comment={md?.data.comment}
>
<Fragment slot="header-content">
- <PostInfo title={md?.data.title || (i18n(I18nKey.about) as string)} />
+ <PostInfo title={md?.data.title || t.navigation.about()} />
</Fragment>
<Markdown
bidirectional-references={md
src/pages/links.astro
@@ -3,10 +3,9 @@ import { linksConfig } from '@/config';
import PostInfo from '@components/misc/PostInfo.astro';
import ImageWrapper from '@components/utils/ImageWrapper.astro';
import Markdown from '@components/utils/Markdown.astro';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import PostPageLayout from '@layouts/PostPageLayout.astro';
import { getAllReferences } from '@utils/content-utils';
+import { t } from '@utils/i18n';
import { getEntry, render } from 'astro:content';
const md = await getEntry('spec', 'links');
@@ -39,12 +38,12 @@ const groupHeadings = linksConfig.items.map((item) => ({
---
<PostPageLayout
- title={i18n(I18nKey.links) as string}
+ title={t.navigation.friendLinks()}
comment={md?.data.comment}
headings={[...groupHeadings, ...headings]}
>
<Fragment slot="header-content">
- <PostInfo title={i18n(I18nKey.links) as string} />
+ <PostInfo title={t.navigation.friendLinks()} />
</Fragment>
{
linksConfig.items.map((item) => (
src/types/config.d.ts
@@ -1,4 +1,4 @@
-import type I18nKey from '@i18n/I18nKey';
+import type { Locales } from 'i18n/i18n-types';
// ============================================================================
export type Favicon = {
@@ -23,7 +23,7 @@ export type ButtonSubConfig<T extends string> = T extends 'text'
*
* 按钮的文本。
*/
- text: string | I18nKey;
+ text: string;
} & (
| {
/**
@@ -68,7 +68,7 @@ export type ButtonSubConfig<T extends string> = T extends 'text'
*
* 按钮的文本。
*/
- text?: string | I18nKey;
+ text?: string;
} & (
| {
/**
@@ -122,7 +122,7 @@ export type SiteConfig = {
*
* 站点的语言。
*/
- lang: string;
+ lang: Locales;
/**
* The time when the site was created.
*
@@ -345,7 +345,7 @@ export type NavbarConfig = {
*
* 组的标题。
*/
- title: string | I18nKey;
+ title: string;
/**
* The items displayed in the group.
*
src/utils/content-utils.ts
@@ -1,8 +1,7 @@
+import { t } from './i18n';
import { buildConfig } from '@/config';
import type { BlogPostData } from '@/types/data';
import type { BlogPost } from '@/types/data';
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
import { type CollectionEntry, getCollection, render } from 'astro:content';
import dayjs from 'dayjs';
@@ -48,7 +47,7 @@ export async function getCategories(): Promise<Map<string, number>> {
const categoryMap = new Map<string, number>();
allBlogPosts.forEach((post) => {
- const category = post.data.category || (i18n(I18nKey.uncategorized) as string);
+ const category = post.data.category || t.meta.unCategorized();
categoryMap.set(category, (categoryMap.get(category) || 0) + 1);
});
@@ -72,12 +71,12 @@ export async function getTags(): Promise<Map<string, number>> {
export function getCategoryUrl(category: string | undefined) {
return category
? `/archives/categories/${category.replaceAll(/[\\/]/g, '-')}/1/`
- : `/archives/categories/${I18nKey.uncategorized}/1/`;
+ : `/archives/categories/uncategorized/1/`;
}
export function getTagUrl(tag: string) {
- return tag === i18n(I18nKey.untagged)
- ? `/archives/tags/${I18nKey.untagged}/1`
+ return tag === t.meta.unTagged()
+ ? `/archives/tags/untagged/1`
: `/archives/tags/${tag.replaceAll(/[\\/]/g, '-')}/1`;
}
src/utils/i18n.ts
@@ -0,0 +1,4 @@
+import originalL from '../../i18n/i18n-node';
+import { siteConfig } from '../config';
+
+export const t = originalL[siteConfig.lang].web;
src/config.ts
@@ -1,8 +1,7 @@
// WARNING: This file will be bundled into the build product.
// DO NOT add any sensitive information here.
// 警告: 该文件会被打包到构建产物中, 不要在此添加任何敏感信息
-import I18nKey from './i18n/I18nKey';
-import { getRandomPost } from './scripts/utils';
+import L from '../i18n/i18n-node';
import type {
ArticleConfig,
AsideConfig,
@@ -21,7 +20,7 @@ import type {
export const siteConfig: SiteConfig = {
title: 'Astral Halo',
subtitle: 'A static blog template powered by Astro',
- lang: 'en', // "en" | "zh_CN" | "zh_TW"
+ lang: 'en',
createAt: new Date('2025-01-01'),
postsPerPage: 10,
banner: {
@@ -39,6 +38,9 @@ export const siteConfig: SiteConfig = {
},
};
+// To avoid circular dependency
+const t = L[siteConfig.lang].web;
+
export const buildConfig: BuildConfig = {
showDraftsOnDev: true,
inferRemoteImageSize: {
@@ -90,37 +92,29 @@ export const linksConfig: LinksConfig = {
export const navbarConfig: NavbarConfig = {
navbarCenterItems: [
{
- title: I18nKey.archive,
+ title: t.navigation.archive.title(),
items: [
- { text: I18nKey.time, href: '/archives/' },
- { text: I18nKey.categories, href: '/archives/categories/' },
- { text: I18nKey.tags, href: '/archives/tags/' },
+ { text: t.navigation.archive.time(), href: '/archives/' },
+ { text: t.navigation.archive.categories(), href: '/archives/categories/' },
+ { text: t.navigation.archive.tags(), href: '/archives/tags/' },
],
},
- { text: I18nKey.links, href: '/links/' },
- { text: I18nKey.about, href: '/about/' },
+ { text: t.navigation.friendLinks(), href: '/links/' },
+ { text: t.navigation.about(), href: '/about/' },
],
navbarRightItems: {
onlyWide: [
{
icon: 'material-symbols:rss-feed-rounded',
- text: I18nKey.subscribe,
+ text: t.button.subscribe(),
href: '/rss.xml',
blank: true,
},
- {
- icon: 'material-symbols:casino',
- text: I18nKey.randomPost,
- onclick: {
- id: 'random-post-btn',
- function: getRandomPost,
- },
- },
],
always: [
{
icon: 'material-symbols:search-rounded',
- text: I18nKey.search,
+ text: t.button.search(),
onclick: 'search_modal.showModal()',
},
],