diff --git a/src/components/Navbar.astro b/src/components/Navbar.astro
index 25787a9..18e69f4 100644
--- a/src/components/Navbar.astro
+++ b/src/components/Navbar.astro
@@ -2,7 +2,22 @@
import Button from "./control/Button.astro";
import { Icon } from 'astro-icon/components';
import DisplaySetting from "./widget/DisplaySetting.astro";
+import {getConfig} from "../utils/config-utils";
+import I18nKey from "../i18n/i18nKey";
+import {i18n} from "../i18n/translation";
const className = Astro.props.class;
+
+function isI18nKey(key: string): key is I18nKey {
+ return Object.values(I18nKey).includes(key);
+}
+
+function getLinkName(name: string) {
+ if (isI18nKey(name)) {
+ return i18n(name);
+ }
+ return name;
+}
+
---
diff --git a/src/components/PostMetadata.astro b/src/components/PostMetadata.astro
index 522bc51..97c8632 100644
--- a/src/components/PostMetadata.astro
+++ b/src/components/PostMetadata.astro
@@ -1,6 +1,8 @@
---
import {formatDateToYYYYMMDD} from "../utils/date-utils";
import { Icon } from 'astro-icon/components';
+import {i18n} from "../i18n/translation";
+import I18nKey from "../i18n/i18nKey";
interface Props {
class: string;
@@ -38,7 +40,7 @@ const className = Astro.props.class;
{category}
)}
- {!categories &&
Uncategorized
}
+ {!categories &&
{i18n(I18nKey.uncategorized)}
}
@@ -49,7 +51,7 @@ const className = Astro.props.class;
-
{remarkPluginFrontmatter.words} words
+
{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}
|
-
{remarkPluginFrontmatter.minutes} minutes
+
{remarkPluginFrontmatter.minutes} {" " + i18n(I18nKey.minutesCount)}
diff --git a/src/components/widget/DisplaySetting.astro b/src/components/widget/DisplaySetting.astro
index d337a3a..19971d5 100644
--- a/src/components/widget/DisplaySetting.astro
+++ b/src/components/widget/DisplaySetting.astro
@@ -1,5 +1,7 @@
---
import {getConfig} from "../../utils/config-utils";
+import {i18n} from "../../i18n/translation";
+import I18nKey from "../../i18n/i18nKey";
const hueSet: number[] = [0, 30, 60, 180, 250, 270, 300, 330, 345];
const enableBanner = getConfig().banner.enable;
@@ -13,7 +15,7 @@ const enableBanner = getConfig().banner.enable;
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-12px] before:top-[5.5px]"
>
- Primary Color
+ {i18n(I18nKey.primaryColor)}
{0}
diff --git a/src/components/widget/RecentPost.astro b/src/components/widget/RecentPost.astro
index b14b934..b40c112 100644
--- a/src/components/widget/RecentPost.astro
+++ b/src/components/widget/RecentPost.astro
@@ -2,6 +2,8 @@
import WidgetLayout from "./WidgetLayout.astro";
import ButtonLink from "../control/ButtonLink.astro";
import {getPostUrlBySlug, getSortedPosts} from "../../utils/content-utils";
+import {i18n} from "../../i18n/translation";
+import I18nKey from "../../i18n/i18nKey";
let posts = await getSortedPosts()
@@ -11,7 +13,7 @@ posts = posts.slice(0, LIMIT)
// console.log(posts)
---
-
+
{posts.map(post =>
{post.data.title}
)}
diff --git a/src/components/widget/Tag.astro b/src/components/widget/Tag.astro
index 33c1c72..96b3903 100644
--- a/src/components/widget/Tag.astro
+++ b/src/components/widget/Tag.astro
@@ -3,11 +3,13 @@
import WidgetLayout from "./WidgetLayout.astro";
import ButtonTag from "../control/ButtonTag.astro";
import {getTagList} from "../../utils/content-utils";
+import {i18n} from "../../i18n/translation";
+import I18nKey from "../../i18n/i18nKey";
const tags = await getTagList();
---
-
+
{tags.map(t => (
diff --git a/src/i18n/i18nKey.ts b/src/i18n/i18nKey.ts
new file mode 100644
index 0000000..347046f
--- /dev/null
+++ b/src/i18n/i18nKey.ts
@@ -0,0 +1,26 @@
+enum I18nKey {
+ home = "home",
+ about = "about",
+ archive = "archive",
+
+ tags = "tags",
+ categories = "categories",
+ recentPosts = "recentPosts",
+
+ comments = "comments",
+
+ untitled = "untitled",
+ uncategorized = "uncategorized",
+ noTags = "noTags",
+
+ wordCount = "wordCount",
+ wordsCount = "wordsCount",
+ minuteCount = "minuteCount",
+ minutesCount = "minutesCount",
+ postCount = "postCount",
+ postsCount = "postsCount",
+
+ primaryColor = "primaryColor",
+}
+
+export default I18nKey;
\ No newline at end of file
diff --git a/src/i18n/languages/en.ts b/src/i18n/languages/en.ts
new file mode 100644
index 0000000..a155b37
--- /dev/null
+++ b/src/i18n/languages/en.ts
@@ -0,0 +1,27 @@
+import type { Translation } from "../translation.ts";
+import Key from "../i18nKey.ts";
+
+export const en: Translation = {
+ [Key.home]: "Home",
+ [Key.about]: "About",
+ [Key.archive]: "Archive",
+
+ [Key.tags]: "Tags",
+ [Key.categories]: "Categories",
+ [Key.recentPosts]: "Recent Posts",
+
+ [Key.comments]: "Comments",
+
+ [Key.untitled]: "Untitled",
+ [Key.uncategorized]: "Uncategorized",
+ [Key.noTags]: "No Tags",
+
+ [Key.wordCount]: "word",
+ [Key.wordsCount]: "words",
+ [Key.minuteCount]: "minute",
+ [Key.minutesCount]: "minutes",
+ [Key.postCount]: "post",
+ [Key.postsCount]: "posts",
+
+ [Key.primaryColor]: "Primary Color",
+};
diff --git a/src/i18n/languages/zh_CN.ts b/src/i18n/languages/zh_CN.ts
new file mode 100644
index 0000000..8cffc66
--- /dev/null
+++ b/src/i18n/languages/zh_CN.ts
@@ -0,0 +1,27 @@
+import type { Translation } from "../translation.ts";
+import Key from "../i18nKey.ts";
+
+export const zh_CN: Translation = {
+ [Key.home]: "主页",
+ [Key.about]: "关于",
+ [Key.archive]: "归档",
+
+ [Key.tags]: "标签",
+ [Key.categories]: "分类",
+ [Key.recentPosts]: "最新文章",
+
+ [Key.comments]: "评论",
+
+ [Key.untitled]: "无标题",
+ [Key.uncategorized]: "未分类",
+ [Key.noTags]: "无标签",
+
+ [Key.wordCount]: "字",
+ [Key.wordsCount]: "字",
+ [Key.minuteCount]: "分钟",
+ [Key.minutesCount]: "分钟",
+ [Key.postCount]: "篇文章",
+ [Key.postsCount]: "篇文章",
+
+ [Key.primaryColor]: "主题色",
+};
\ No newline at end of file
diff --git a/src/i18n/languages/zh_TW.ts b/src/i18n/languages/zh_TW.ts
new file mode 100644
index 0000000..2482217
--- /dev/null
+++ b/src/i18n/languages/zh_TW.ts
@@ -0,0 +1,27 @@
+import type { Translation } from "../translation.ts";
+import Key from "../i18nKey.ts";
+
+export const zh_TW: Translation = {
+ [Key.home]: "首頁",
+ [Key.about]: "關於",
+ [Key.archive]: "彙整",
+
+ [Key.tags]: "標籤",
+ [Key.categories]: "分類",
+ [Key.recentPosts]: "最新文章",
+
+ [Key.comments]: "評論",
+
+ [Key.untitled]: "無標題",
+ [Key.uncategorized]: "未分類",
+ [Key.noTags]: "無標籤",
+
+ [Key.wordCount]: "字",
+ [Key.wordsCount]: "字",
+ [Key.minuteCount]: "分鐘",
+ [Key.minutesCount]: "分鐘",
+ [Key.postCount]: "篇文章",
+ [Key.postsCount]: "篇文章",
+
+ [Key.primaryColor]: "主題色",
+};
diff --git a/src/i18n/translation.ts b/src/i18n/translation.ts
new file mode 100644
index 0000000..ed1e979
--- /dev/null
+++ b/src/i18n/translation.ts
@@ -0,0 +1,30 @@
+import {en} from "./languages/en.ts";
+import {zh_TW} from "./languages/zh_TW.ts";
+import {zh_CN} from "./languages/zh_CN.ts";
+import type I18nKey from "./i18nKey.ts";
+import {getConfig} from "../utils/config-utils.ts";
+
+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 {
+ lang = lang.toLowerCase();
+ return map[lang] || defaultTranslation;
+}
+
+export function i18n(key: I18nKey): string {
+ const lang = getConfig().lang || "en";
+ return getTranslation(lang)[key];
+}
\ No newline at end of file
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index f77cd20..0200b98 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -54,6 +54,11 @@ if (!banner || typeof banner !== 'string' || banner.trim() === '') {
// TODO don't use post cover as banner for now
banner = viConf.banner.url;
+let pageTitle = getConfig().title;
+if (title) {
+ pageTitle = `${title} - ${pageTitle}`;
+}
+
---
@@ -61,6 +66,8 @@ banner = viConf.banner.url;
+ {pageTitle}
+
diff --git a/src/pages/about.astro b/src/pages/about.astro
index d5b38fc..7f79402 100644
--- a/src/pages/about.astro
+++ b/src/pages/about.astro
@@ -3,13 +3,15 @@
import MainGridLayout from "../layouts/MainGridLayout.astro";
import { getEntry } from 'astro:content'
+import {i18n} from "../i18n/translation";
+import I18nKey from "../i18n/i18nKey";
const aboutPost = await getEntry('spec', 'about')
const { Content } = await aboutPost.render()
---
-
+
diff --git a/src/pages/archive/category/[category].astro b/src/pages/archive/category/[category].astro
index 00cfebf..dd4071b 100644
--- a/src/pages/archive/category/[category].astro
+++ b/src/pages/archive/category/[category].astro
@@ -3,6 +3,8 @@
import {getSortedPosts} from "../../../utils/content-utils";
import MainGridLayout from "../../../layouts/MainGridLayout.astro";
import ArchivePanel from "../../../components/ArchivePanel.astro";
+import {i18n} from "../../../i18n/translation";
+import I18nKey from "../../../i18n/i18nKey";
export async function getStaticPaths() {
@@ -30,6 +32,6 @@ const { category } = Astro.params;
---
-
+
diff --git a/src/pages/archive/index.astro b/src/pages/archive/index.astro
index 5d2544f..85d6fae 100644
--- a/src/pages/archive/index.astro
+++ b/src/pages/archive/index.astro
@@ -2,9 +2,11 @@
import { getCollection, getEntry } from "astro:content";
import MainGridLayout from "../../layouts/MainGridLayout.astro";
import ArchivePanel from "../../components/ArchivePanel.astro";
+import {i18n} from "../../i18n/translation";
+import I18nKey from "../../i18n/i18nKey";
---
-
+
diff --git a/src/pages/archive/tag/[tag].astro b/src/pages/archive/tag/[tag].astro
index f57a0b7..466cba4 100644
--- a/src/pages/archive/tag/[tag].astro
+++ b/src/pages/archive/tag/[tag].astro
@@ -3,7 +3,8 @@
import {getSortedPosts} from "../../../utils/content-utils";
import MainGridLayout from "../../../layouts/MainGridLayout.astro";
import ArchivePanel from "../../../components/ArchivePanel.astro";
-
+import {i18n} from "../../../i18n/translation";
+import I18nKey from "../../../i18n/i18nKey";
export async function getStaticPaths() {
@@ -29,6 +30,6 @@ const { tag } = Astro.params;
---
-
+
diff --git a/src/pages/posts/[slug].astro b/src/pages/posts/[slug].astro
index 37eefcd..f00efd0 100644
--- a/src/pages/posts/[slug].astro
+++ b/src/pages/posts/[slug].astro
@@ -9,6 +9,8 @@ import PostMetadata from "../../components/PostMetadata.astro";
import {getPostUrlBySlug} from "../../utils/content-utils";
import Button from "../../components/control/Button.astro";
import {getConfig} from "../../utils/config-utils";
+import {i18n} from "../../i18n/translation";
+import I18nKey from "../../i18n/i18nKey";
export async function getStaticPaths() {
const blogEntries = await getCollection('posts');
@@ -25,7 +27,7 @@ const { remarkPluginFrontmatter } = await entry.render();
const enableBanner = getConfig().banner.enable;
---
-
+
-
{remarkPluginFrontmatter.words} words
+
{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}
-
{remarkPluginFrontmatter.minutes} minutes
+
{remarkPluginFrontmatter.minutes} {" " + i18n(I18nKey.minutesCount)}
diff --git a/src/utils/config-utils.ts b/src/utils/config-utils.ts
index f2a9e22..d2c69a7 100644
--- a/src/utils/config-utils.ts
+++ b/src/utils/config-utils.ts
@@ -1,9 +1,12 @@
+// @ts-ignore
import _config from '../../vivia.config.yml';
interface ViviaConfig {
+ title: string;
menu: {
[key: string]: string;
};
+ lang: string;
appearance: {
hue: number;
};
diff --git a/vivia.config.yml b/vivia.config.yml
index 8b5e76a..5c6be76 100644
--- a/vivia.config.yml
+++ b/vivia.config.yml
@@ -1,6 +1,14 @@
+title: Fuwari
+menu:
+ home: /
+ archive: /archive
+ about: /about
+
appearance:
hue: 250
+lang: en
+
banner:
enable: true
url: https://saicaca.github.io/vivia-preview/assets/banner.jpg