@windcoder/astro-theme-orchid 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/scripts/generate-lqips.mjs +239 -20
- package/src/components/AlbumCard.astro +4 -1
- package/src/components/BaseLayout.astro +209 -64
- package/src/components/DisplaySettings.astro +383 -0
- package/src/components/Header.astro +56 -2
- package/src/components/MusicPlayer.astro +45 -5
- package/src/components/MusicWidget.astro +265 -0
- package/src/components/PhotoCard.astro +13 -1
- package/src/components/PostCard.astro +9 -2
- package/src/components/SakuraEffect.astro +126 -28
- package/src/components/Sidebar.astro +28 -7
- package/src/i18n/en.ts +36 -0
- package/src/i18n/ja.ts +213 -0
- package/src/i18n/ko.ts +212 -0
- package/src/i18n/resolve.ts +10 -1
- package/src/i18n/ru.ts +213 -0
- package/src/i18n/zh-CN.ts +37 -0
- package/src/i18n/zh-TW.ts +208 -0
- package/src/index.ts +24 -0
- package/src/plugins/rehype.ts +66 -2
- package/src/routes/friends.astro +10 -2
- package/src/routes/page/[page].astro +3 -0
- package/src/routes/posts/[...slug].astro +7 -2
- package/src/routes/search.astro +24 -0
- package/src/routes/sponsor.astro +17 -4
- package/src/schema.ts +364 -71
- package/src/scripts/app.ts +817 -37
- package/src/styles/global.css +865 -36
- package/src/utils/gallery.ts +3 -1
- package/src/utils/image.ts +90 -0
- package/src/utils/lqip.ts +21 -12
- package/src/utils/thumb.ts +31 -0
- package/src/vite.ts +95 -8
|
@@ -4,13 +4,16 @@ import config from "virtual:theme/config";
|
|
|
4
4
|
import { Icon } from "astro-icon/components";
|
|
5
5
|
import { t } from "../i18n/index.js";
|
|
6
6
|
import { resolveThemeIcon } from "../icons.js";
|
|
7
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
7
8
|
import {
|
|
8
9
|
collectCategories,
|
|
9
10
|
collectTags,
|
|
11
|
+
countTotalWords,
|
|
10
12
|
daysSince,
|
|
11
13
|
type PostLike,
|
|
12
14
|
} from "../utils.js";
|
|
13
15
|
import Calendar from "./Calendar.astro";
|
|
16
|
+
import MusicWidget from "./MusicWidget.astro";
|
|
14
17
|
import type { TocHeading } from "./Toc.astro";
|
|
15
18
|
|
|
16
19
|
interface Props {
|
|
@@ -38,6 +41,8 @@ const shownTags = tagCount > 0 ? tags.slice(0, tagCount) : tags;
|
|
|
38
41
|
const tagHidden = tags.length - shownTags.length;
|
|
39
42
|
|
|
40
43
|
const runDays = config.since ? daysSince(config.since) : null;
|
|
44
|
+
// §4.99 站点信息总字数:全站文章 words 之和(frontmatter words 字段),0 时隐藏该行
|
|
45
|
+
const totalWords = countTotalWords(posts);
|
|
41
46
|
|
|
42
47
|
// widget 开关与顺序(P2 侧栏改造):config.sidebar.widgets 数组即渲染顺序;
|
|
43
48
|
// 两个方向的显隐(对齐参考模板 showOnPostPage/hideOnNonPostPage):
|
|
@@ -61,12 +66,18 @@ const annLink = Boolean(ann.link.text && ann.link.url);
|
|
|
61
66
|
const profile = config.profile;
|
|
62
67
|
const showProfile = Boolean(profile.name || profile.avatar || profile.bio);
|
|
63
68
|
const profileInitial = (profile.name || "?").trim().slice(0, 1) || "?";
|
|
69
|
+
// 头像三格式解析(§4.97):相对路径相对站点 src/ 构建期解析
|
|
70
|
+
const profileAvatar = profile.avatar
|
|
71
|
+
? await resolveSiteImage(profile.avatar)
|
|
72
|
+
: "";
|
|
64
73
|
|
|
65
74
|
// 广告 widget(P2 保留项,默认关):enabled + 至少一项内容 + 未过期才渲染
|
|
66
75
|
const ad = config.advertisement;
|
|
67
76
|
const adExpired = ad.expireDate ? new Date() > new Date(ad.expireDate) : false;
|
|
68
77
|
const showAd =
|
|
69
78
|
ad.enabled && Boolean(ad.image || ad.content || ad.link.text) && !adExpired;
|
|
79
|
+
// 广告图三格式解析(§4.97)
|
|
80
|
+
const adImage = ad.image ? await resolveSiteImage(ad.image) : "";
|
|
70
81
|
|
|
71
82
|
// 友链 widget(决策 8):与友链页同源 themeConfig.friends.list;
|
|
72
83
|
// random=false 时 featured 优先、其余按 weight 降序,取前 count 个。
|
|
@@ -84,6 +95,12 @@ if (fw?.type === "friends") {
|
|
|
84
95
|
});
|
|
85
96
|
sideFriends = pool.slice(0, sc.count);
|
|
86
97
|
}
|
|
98
|
+
// 友链头像三格式解析(§4.97):空串跳过(留空用字头像)
|
|
99
|
+
const sideFriendImgs = await Promise.all(
|
|
100
|
+
sideFriends.map((f) =>
|
|
101
|
+
f.image ? resolveSiteImage(f.image) : Promise.resolve(""),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
87
104
|
---
|
|
88
105
|
|
|
89
106
|
<aside class="sidebar">
|
|
@@ -102,13 +119,13 @@ if (fw?.type === "friends") {
|
|
|
102
119
|
{profile.url ? (
|
|
103
120
|
<a class="profile-avatar-link" href={profile.url} aria-label={profile.name}>
|
|
104
121
|
{profile.avatar ? (
|
|
105
|
-
<img class="profile-avatar" src={
|
|
122
|
+
<img class="profile-avatar" src={profileAvatar} alt={profile.name} loading="lazy" />
|
|
106
123
|
) : (
|
|
107
124
|
<span class="profile-avatar avatar-fallback">{profileInitial}</span>
|
|
108
125
|
)}
|
|
109
126
|
</a>
|
|
110
127
|
) : profile.avatar ? (
|
|
111
|
-
<img class="profile-avatar" src={
|
|
128
|
+
<img class="profile-avatar" src={profileAvatar} alt={profile.name} loading="lazy" />
|
|
112
129
|
) : (
|
|
113
130
|
<span class="profile-avatar avatar-fallback">{profileInitial}</span>
|
|
114
131
|
)}
|
|
@@ -132,7 +149,7 @@ if (fw?.type === "friends") {
|
|
|
132
149
|
// 未命中跳过图标只渲染文字
|
|
133
150
|
const icon = lk.icon
|
|
134
151
|
? lk.icon.startsWith("<")
|
|
135
|
-
? { html: lk.icon }
|
|
152
|
+
? { html: lk.icon, name: undefined }
|
|
136
153
|
: resolveThemeIcon(lk.icon)
|
|
137
154
|
: null;
|
|
138
155
|
return (
|
|
@@ -218,10 +235,10 @@ if (fw?.type === "friends") {
|
|
|
218
235
|
<section class:list={["side-card", "card", "reveal", { "side-hide-m": w.hideOnMobile }]}>
|
|
219
236
|
<h3>{t('friendLinks')}</h3>
|
|
220
237
|
<div class="side-friends">
|
|
221
|
-
{sideFriends.map((f) => (
|
|
238
|
+
{sideFriends.map((f, i) => (
|
|
222
239
|
<a href={f.site} target="_blank" rel="noopener" title={f.title}>
|
|
223
240
|
{f.image ? (
|
|
224
|
-
<img src={
|
|
241
|
+
<img src={sideFriendImgs[i]} alt={f.title} loading="lazy" />
|
|
225
242
|
) : (
|
|
226
243
|
<span class="avatar-fallback">{f.title.slice(0, 1)}</span>
|
|
227
244
|
)}
|
|
@@ -250,6 +267,9 @@ if (fw?.type === "friends") {
|
|
|
250
267
|
</nav>
|
|
251
268
|
</section>
|
|
252
269
|
)}
|
|
270
|
+
{w.type === 'music' && config.music.enabled && config.music.playlist.length > 0 && (
|
|
271
|
+
<MusicWidget hideMobile={w.hideOnMobile} />
|
|
272
|
+
)}
|
|
253
273
|
{w.type === 'siteInfo' && (
|
|
254
274
|
<section class:list={["side-card", "card", "reveal", { "side-hide-m": w.hideOnMobile }]}>
|
|
255
275
|
<h3>{t('siteInfo')}</h3>
|
|
@@ -258,6 +278,7 @@ if (fw?.type === "friends") {
|
|
|
258
278
|
<div class="row"><span>{t('categories')}</span><b>{cats.length}</b></div>
|
|
259
279
|
<div class="row"><span>{t('tags')}</span><b>{tags.length}</b></div>
|
|
260
280
|
{runDays !== null && <div class="row"><span>{t('running')}</span><b>{t('daysCount', { n: runDays })}</b></div>}
|
|
281
|
+
{totalWords > 0 && <div class="row"><span>{t('totalWords')}</span><b>{totalWords.toLocaleString()}</b></div>}
|
|
261
282
|
</div>
|
|
262
283
|
</section>
|
|
263
284
|
)}
|
|
@@ -278,10 +299,10 @@ if (fw?.type === "friends") {
|
|
|
278
299
|
{ad.image &&
|
|
279
300
|
(ad.imageLink ? (
|
|
280
301
|
<a href={ad.imageLink} target="_blank" rel="noopener">
|
|
281
|
-
<img src={
|
|
302
|
+
<img src={adImage} alt={ad.imageAlt} loading="lazy" />
|
|
282
303
|
</a>
|
|
283
304
|
) : (
|
|
284
|
-
<img src={
|
|
305
|
+
<img src={adImage} alt={ad.imageAlt} loading="lazy" />
|
|
285
306
|
))}
|
|
286
307
|
{ad.content && <p>{ad.content}</p>}
|
|
287
308
|
{ad.link.text && (
|
package/src/i18n/en.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { UiKey } from "./zh-CN.js";
|
|
|
5
5
|
|
|
6
6
|
const en: Record<UiKey, string> = {
|
|
7
7
|
skipToContent: "Skip to content",
|
|
8
|
+
scrollDown: "Scroll down",
|
|
8
9
|
backToTop: "Back to top",
|
|
9
10
|
mainNav: "Main navigation",
|
|
10
11
|
toggleMode: "Toggle dark mode",
|
|
@@ -97,6 +98,8 @@ const en: Record<UiKey, string> = {
|
|
|
97
98
|
searchNoResults: "No results found",
|
|
98
99
|
searchUnavailable: "Search index available after build",
|
|
99
100
|
searchTypeSomething: "Type to search",
|
|
101
|
+
searchMore: "View all {n} results",
|
|
102
|
+
searchResultsTotal: "{n} results",
|
|
100
103
|
galleryPhotos: "{n} photos",
|
|
101
104
|
backToAlbums: "Back to albums",
|
|
102
105
|
searchAlbums: "Search albums…",
|
|
@@ -168,6 +171,39 @@ const en: Record<UiKey, string> = {
|
|
|
168
171
|
codeFoldCollapse: "Collapse code",
|
|
169
172
|
codeFoldExpanded: "Code block expanded",
|
|
170
173
|
codeFoldCollapsed: "Code block collapsed",
|
|
174
|
+
/* Wallpaper (§4.98): header control button aria-label/title */
|
|
175
|
+
wpPlay: "Play wallpaper video",
|
|
176
|
+
wpPause: "Pause wallpaper video",
|
|
177
|
+
wpPrev: "Previous video",
|
|
178
|
+
wpNext: "Next video",
|
|
179
|
+
wpMode: "Switch wallpaper mode",
|
|
180
|
+
/* Display settings panel (§4.103): gear entry + three tabs + group controls */
|
|
181
|
+
displaySettings: "Display Settings",
|
|
182
|
+
tabAppearance: "Appearance",
|
|
183
|
+
tabWallpaper: "Wallpaper",
|
|
184
|
+
tabEffects: "Effects",
|
|
185
|
+
wallpaperMode: "Wallpaper Mode",
|
|
186
|
+
modeBanner: "Banner",
|
|
187
|
+
modeFullscreen: "Fullscreen",
|
|
188
|
+
modeOverlay: "Overlay",
|
|
189
|
+
modeNone: "Solid Color",
|
|
190
|
+
fullscreenLayout: "Fullscreen Layout",
|
|
191
|
+
layoutClassic: "Classic",
|
|
192
|
+
layoutHero: "Hero",
|
|
193
|
+
wallpaperSettings: "Wallpaper Settings",
|
|
194
|
+
homeWallpaperTitle: "Home Wallpaper Title",
|
|
195
|
+
wallpaperCarousel: "Wallpaper Carousel",
|
|
196
|
+
wavesAnimation: "Waves Animation",
|
|
197
|
+
gradientTransition: "Gradient Transition",
|
|
198
|
+
overlaySettings: "Transparency",
|
|
199
|
+
wallpaperOpacity: "Wallpaper Opacity",
|
|
200
|
+
backgroundBlur: "Background Blur",
|
|
201
|
+
cardOpacity: "Card Opacity",
|
|
202
|
+
sakuraEffect: "Sakura Effect",
|
|
203
|
+
/* Site info total words (§4.99): sidebar siteInfo card */
|
|
204
|
+
totalWords: "Total words",
|
|
205
|
+
/* Sidebar music widget (§4.100): title; play/prev/next reuse music* keys */
|
|
206
|
+
musicWidget: "Music",
|
|
171
207
|
};
|
|
172
208
|
|
|
173
209
|
export default en;
|
package/src/i18n/ja.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日本語言語パック:Record<UiKey, string> 强制与中文键源一一对应,缺译会在编译期报错。
|
|
3
|
+
*/
|
|
4
|
+
import type { UiKey } from "./zh-CN.js";
|
|
5
|
+
|
|
6
|
+
const ja: Record<UiKey, string> = {
|
|
7
|
+
skipToContent: "本文へスキップ",
|
|
8
|
+
scrollDown: "下にスクロール",
|
|
9
|
+
backToTop: "トップへ戻る",
|
|
10
|
+
mainNav: "メインナビゲーション",
|
|
11
|
+
toggleMode: "ダークモード切り替え",
|
|
12
|
+
menu: "メニュー",
|
|
13
|
+
palette: "テーマカラー",
|
|
14
|
+
paletteTitle: "アクセントカラー",
|
|
15
|
+
reset: "リセット",
|
|
16
|
+
hueValue: "色相 {n}",
|
|
17
|
+
pinned: "ピン留め",
|
|
18
|
+
wordsCount: "{n} 文字",
|
|
19
|
+
minutesCount: "約 {n} 分",
|
|
20
|
+
prevNext: "前後の記事",
|
|
21
|
+
prevPost: "前の記事",
|
|
22
|
+
nextPost: "次の記事",
|
|
23
|
+
firstPost: "これが最初の記事です",
|
|
24
|
+
lastPost: "これが最新の記事です",
|
|
25
|
+
relatedPosts: "関連記事",
|
|
26
|
+
randomPosts: "ランダム記事",
|
|
27
|
+
smartRecommend: "スマート",
|
|
28
|
+
randomRecommend: "ランダム",
|
|
29
|
+
noRelatedPosts: "関連記事はありません",
|
|
30
|
+
noRandomPosts: "ランダム記事はありません",
|
|
31
|
+
licensePrefix: "この記事は",
|
|
32
|
+
licenseSuffix:
|
|
33
|
+
"ライセンスで提供しています。転載の際は出典と作者を明記してください。",
|
|
34
|
+
toc: "目次",
|
|
35
|
+
tocTitle: "目 次",
|
|
36
|
+
pagination: "ページネーション",
|
|
37
|
+
prevPage: "前のページ",
|
|
38
|
+
nextPage: "次のページ",
|
|
39
|
+
pageOf: "{total} ページ中 {current} ページ目",
|
|
40
|
+
closeAnnouncement: "お知らせを閉じる",
|
|
41
|
+
closeAd: "広告を閉じる",
|
|
42
|
+
categories: "カテゴリー",
|
|
43
|
+
tags: "タグ",
|
|
44
|
+
friendLinks: "リンク",
|
|
45
|
+
siteInfo: "サイト情報",
|
|
46
|
+
posts: "記事",
|
|
47
|
+
running: "稼働期間",
|
|
48
|
+
daysCount: "{n} 日",
|
|
49
|
+
dayPostsCount: "{n} 件",
|
|
50
|
+
heatWeekTip: "{m} 第 {w} 週 · {n}",
|
|
51
|
+
tagsMore: "…もっと見る",
|
|
52
|
+
calendarHome: "今月へ戻る",
|
|
53
|
+
calendarPick: "年月を選択",
|
|
54
|
+
layoutSwitch: "リスト / グリッド表示切り替え",
|
|
55
|
+
layoutList: "リスト表示",
|
|
56
|
+
layoutGrid: "グリッド表示",
|
|
57
|
+
emptyPosts: "まだ記事がありません。content/posts で書きましょう。",
|
|
58
|
+
pageN: "{n} ページ目",
|
|
59
|
+
archive: "アーカイブ",
|
|
60
|
+
home: "ホーム",
|
|
61
|
+
categoryNav: "カテゴリーナビゲーション",
|
|
62
|
+
more: "もっと見る",
|
|
63
|
+
totalPosts: "全 {n} 件の記事",
|
|
64
|
+
postsInYear: "{n} 件",
|
|
65
|
+
yearUnit: "{n} 年",
|
|
66
|
+
totalTags: "全 {n} 個のタグ",
|
|
67
|
+
tagTitle: "タグ:{tag}",
|
|
68
|
+
totalCategories: "全 {n} 個のカテゴリー",
|
|
69
|
+
categoryTitle: "カテゴリー:{category}",
|
|
70
|
+
publishedOn: "{date} 公開",
|
|
71
|
+
updatedOn: "{date} 更新",
|
|
72
|
+
pageviews: "閲覧数",
|
|
73
|
+
notFound: "ページが見つかりません",
|
|
74
|
+
notFoundDesc: "ページが存在しないか、移動されました。",
|
|
75
|
+
backHome: "ホームへ戻る",
|
|
76
|
+
about: "私について",
|
|
77
|
+
aboutEmpty: "src/content/spec/about.md にこのページの内容を書いてください",
|
|
78
|
+
avatarAlt: "{name} のアバター",
|
|
79
|
+
noFriends: "リンクはまだありません",
|
|
80
|
+
applyFriend: "リンク申請",
|
|
81
|
+
/* 番組視聴ページ */
|
|
82
|
+
bangumiEmpty: "視聴中の番組はまだありません",
|
|
83
|
+
bangumiFail:
|
|
84
|
+
"番組データの取得に失敗しました。サイトを再ビルドして再試行してください",
|
|
85
|
+
epProgress: "第 {n} / {total} 話まで視聴",
|
|
86
|
+
epProgressOpen: "第 {n} 話まで視聴",
|
|
87
|
+
myRate: "私の評価 {n}",
|
|
88
|
+
guestbook: "ゲストブック",
|
|
89
|
+
guestbookSub: "あなたの足跡を残してください",
|
|
90
|
+
commentComingSoon: "コメント欄は近日公開予定です。お楽しみに。",
|
|
91
|
+
qrAlt: "{name} の QR コード",
|
|
92
|
+
goSponsor: "寄付する",
|
|
93
|
+
qrPlaceholder: "QR コードのプレースホルダー",
|
|
94
|
+
sponsorList: "支援者一覧",
|
|
95
|
+
noSponsors: "まだ支援がありません。最初の支援者になりましょう",
|
|
96
|
+
search: "検索",
|
|
97
|
+
searchShortcut: "/ または Ctrl+K で検索",
|
|
98
|
+
searchLoading: "検索中…",
|
|
99
|
+
searchNoResults: "関連する結果が見つかりません",
|
|
100
|
+
searchUnavailable: "検索インデックスはビルド後に利用可能です",
|
|
101
|
+
searchTypeSomething: "キーワードを入力して検索",
|
|
102
|
+
searchMore: "すべての結果({n} 件)を見る",
|
|
103
|
+
searchResultsTotal: "{n} 件の結果",
|
|
104
|
+
galleryPhotos: "写真 {n} 枚",
|
|
105
|
+
backToAlbums: "アルバムに戻る",
|
|
106
|
+
searchAlbums: "アルバムを検索…",
|
|
107
|
+
allTags: "すべて",
|
|
108
|
+
galleryNoAlbums:
|
|
109
|
+
"アルバムがまだありません。テーマ設定の gallery.albums に追加してください。",
|
|
110
|
+
galleryNoPhotos: "このアルバムには写真がありません",
|
|
111
|
+
/* つぶやきページ */
|
|
112
|
+
momentsTitle: "つぶやき",
|
|
113
|
+
momentsEmpty: "つぶやきはまだありません。",
|
|
114
|
+
encryptedAlbum: "暗号化アルバム",
|
|
115
|
+
searchAlbumNoResults: "一致するアルバムがありません",
|
|
116
|
+
postEncrypted: "暗号化済み",
|
|
117
|
+
passwordProtected: "パスワード保護",
|
|
118
|
+
passwordProtectedDesc:
|
|
119
|
+
"この内容はパスワードで保護されています。閲覧するにはパスワードを入力してください。",
|
|
120
|
+
passwordHint: "ヒント",
|
|
121
|
+
passwordPlaceholder: "パスワードを入力",
|
|
122
|
+
passwordSubmit: "ロック解除",
|
|
123
|
+
passwordError: "パスワードが正しくありません。もう一度お試しください。",
|
|
124
|
+
close: "閉じる",
|
|
125
|
+
prevImage: "前の画像",
|
|
126
|
+
nextImage: "次の画像",
|
|
127
|
+
copied: "コピーしました",
|
|
128
|
+
fullscreen: "全画面",
|
|
129
|
+
slideshow: "自動再生",
|
|
130
|
+
thumbs: "サムネイル",
|
|
131
|
+
/* シェアポスター */
|
|
132
|
+
sharePoster: "シェアポスターを作成",
|
|
133
|
+
generatingPoster: "ポスターを生成中…",
|
|
134
|
+
copyLink: "リンクをコピー",
|
|
135
|
+
savePoster: "ポスターを保存",
|
|
136
|
+
scanToRead: "QR コードを読み取って全文を読む",
|
|
137
|
+
author: "作者",
|
|
138
|
+
supportTitle: "サポート",
|
|
139
|
+
supportDesc: "この記事が役に立ったら、寄付やシェアをいただけると嬉しいです。",
|
|
140
|
+
sponsorMe: "支援する",
|
|
141
|
+
lastModifiedPrefix: "最終更新日 ",
|
|
142
|
+
lastModifiedDaysAgo: "、{days} 日経過",
|
|
143
|
+
lastModifiedOutdated: "一部の内容は古くなっている可能性があります。",
|
|
144
|
+
/* シリーズ */
|
|
145
|
+
series: "シリーズ",
|
|
146
|
+
seriesTotal: "全 {n} 個のシリーズ",
|
|
147
|
+
seriesPostsCount: "{n} 件",
|
|
148
|
+
seriesUpdated: "{date} 更新",
|
|
149
|
+
seriesEmpty:
|
|
150
|
+
"シリーズがまだありません。記事の frontmatter に series を記入すると作成できます。",
|
|
151
|
+
seriesProgress: "{cur} / {total}",
|
|
152
|
+
seriesCatalog: "シリーズ目次",
|
|
153
|
+
/* 音楽プレイヤー */
|
|
154
|
+
musicOpen: "音楽プレイヤー",
|
|
155
|
+
musicClose: "プレイヤーを閉じる",
|
|
156
|
+
musicPlay: "再生",
|
|
157
|
+
musicPause: "一時停止",
|
|
158
|
+
musicPrev: "前の曲",
|
|
159
|
+
musicNext: "次の曲",
|
|
160
|
+
musicModeList: "リスト再生",
|
|
161
|
+
musicModeOne: "1 曲リピート",
|
|
162
|
+
musicModeRandom: "シャッフル再生",
|
|
163
|
+
musicLyrics: "歌詞",
|
|
164
|
+
musicPlaylist: "プレイリスト",
|
|
165
|
+
musicNoLyrics: "歌詞なし",
|
|
166
|
+
musicEmpty: "プレイリストは空です",
|
|
167
|
+
musicSeek: "再生位置",
|
|
168
|
+
musicVolume: "音量",
|
|
169
|
+
l2dHome: "ホームへ戻る",
|
|
170
|
+
l2dScrollTop: "トップへ戻る",
|
|
171
|
+
l2dSleep: "ひと休み",
|
|
172
|
+
l2dSwitchModel: "モデルを変える",
|
|
173
|
+
/* コード折りたたみ */
|
|
174
|
+
codeFoldExpand: "コードを展開",
|
|
175
|
+
codeFoldCollapse: "コードを折りたたむ",
|
|
176
|
+
codeFoldExpanded: "コードブロックを展開しました",
|
|
177
|
+
codeFoldCollapsed: "コードブロックを折りたたみました",
|
|
178
|
+
/* 壁紙 */
|
|
179
|
+
wpPlay: "壁紙動画を再生",
|
|
180
|
+
wpPause: "壁紙動画を一時停止",
|
|
181
|
+
wpPrev: "前の動画",
|
|
182
|
+
wpNext: "次の動画",
|
|
183
|
+
wpMode: "壁紙モードを切り替え",
|
|
184
|
+
/* 表示設定パネル(§4.103):歯車エントリ + 3タブ + 各グループコントロール */
|
|
185
|
+
displaySettings: "表示設定",
|
|
186
|
+
tabAppearance: "外観",
|
|
187
|
+
tabWallpaper: "壁紙",
|
|
188
|
+
tabEffects: "エフェクト",
|
|
189
|
+
wallpaperMode: "壁紙モード",
|
|
190
|
+
modeBanner: "バナー壁紙",
|
|
191
|
+
modeFullscreen: "全画面壁紙",
|
|
192
|
+
modeOverlay: "オーバーレイ透明",
|
|
193
|
+
modeNone: "単色背景",
|
|
194
|
+
fullscreenLayout: "全画面レイアウト",
|
|
195
|
+
layoutClassic: "クラシック",
|
|
196
|
+
layoutHero: "Hero",
|
|
197
|
+
wallpaperSettings: "壁紙設定",
|
|
198
|
+
homeWallpaperTitle: "ホーム壁紙タイトル",
|
|
199
|
+
wallpaperCarousel: "壁紙カルーセル",
|
|
200
|
+
wavesAnimation: "波紋アニメーション",
|
|
201
|
+
gradientTransition: "グラデーション遷移",
|
|
202
|
+
overlaySettings: "透明設定",
|
|
203
|
+
wallpaperOpacity: "壁紙の不透明度",
|
|
204
|
+
backgroundBlur: "背景ぼかし",
|
|
205
|
+
cardOpacity: "カードの不透明度",
|
|
206
|
+
sakuraEffect: "桜エフェクト",
|
|
207
|
+
/* サイト情報の総文字数 */
|
|
208
|
+
totalWords: "総文字数",
|
|
209
|
+
/* サイドバー音楽ウィジェット */
|
|
210
|
+
musicWidget: "音楽",
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default ja;
|
package/src/i18n/ko.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 한국어 언어 팩:Record<UiKey, string> 强制与中文键源一一对应,缺译会在编译期报错。
|
|
3
|
+
*/
|
|
4
|
+
import type { UiKey } from "./zh-CN.js";
|
|
5
|
+
|
|
6
|
+
const ko: Record<UiKey, string> = {
|
|
7
|
+
skipToContent: "본문으로 건어뛰기",
|
|
8
|
+
scrollDown: "아래로 스크롤",
|
|
9
|
+
backToTop: "맨 위로",
|
|
10
|
+
mainNav: "메인 내비게이션",
|
|
11
|
+
toggleMode: "다크 모드 전환",
|
|
12
|
+
menu: "메뉴",
|
|
13
|
+
palette: "테마 색상",
|
|
14
|
+
paletteTitle: "강조 색상",
|
|
15
|
+
reset: "초기화",
|
|
16
|
+
hueValue: "색조 {n}",
|
|
17
|
+
pinned: "고정",
|
|
18
|
+
wordsCount: "{n} 자",
|
|
19
|
+
minutesCount: "약 {n}분",
|
|
20
|
+
prevNext: "이전 / 다음 글",
|
|
21
|
+
prevPost: "이전 글",
|
|
22
|
+
nextPost: "다음 글",
|
|
23
|
+
firstPost: "가장 오래된 글입니다",
|
|
24
|
+
lastPost: "가장 최신 글입니다",
|
|
25
|
+
relatedPosts: "관련 글",
|
|
26
|
+
randomPosts: "랜덤 글",
|
|
27
|
+
smartRecommend: "스마트",
|
|
28
|
+
randomRecommend: "랜덤",
|
|
29
|
+
noRelatedPosts: "관련 글이 없습니다",
|
|
30
|
+
noRandomPosts: "랜덤 글이 없습니다",
|
|
31
|
+
licensePrefix: "이 글은 ",
|
|
32
|
+
licenseSuffix: " 라이선스를 따르며, 전재 시 출처와 저자를 밝혀 주세요.",
|
|
33
|
+
toc: "목차",
|
|
34
|
+
tocTitle: "목 차",
|
|
35
|
+
pagination: "페이지네이션",
|
|
36
|
+
prevPage: "이전 페이지",
|
|
37
|
+
nextPage: "다음 페이지",
|
|
38
|
+
pageOf: "{total} 페이지 중 {current} 페이지",
|
|
39
|
+
closeAnnouncement: "공지 닫기",
|
|
40
|
+
closeAd: "광고 닫기",
|
|
41
|
+
categories: "카테고리",
|
|
42
|
+
tags: "태그",
|
|
43
|
+
friendLinks: "친구 링크",
|
|
44
|
+
siteInfo: "사이트 정보",
|
|
45
|
+
posts: "글",
|
|
46
|
+
running: "운영 기간",
|
|
47
|
+
daysCount: "{n}일",
|
|
48
|
+
dayPostsCount: "{n}편",
|
|
49
|
+
heatWeekTip: "{m} {w}주차 · {n}",
|
|
50
|
+
tagsMore: "…더 보기",
|
|
51
|
+
calendarHome: "이번 달로",
|
|
52
|
+
calendarPick: "연도 및 월 선택",
|
|
53
|
+
layoutSwitch: "목록 / 그리드 보기 전환",
|
|
54
|
+
layoutList: "목록 보기",
|
|
55
|
+
layoutGrid: "그리드 보기",
|
|
56
|
+
emptyPosts: "아직 글이 없습니다. content/posts에서 작성해 보세요.",
|
|
57
|
+
pageN: "{n} 페이지",
|
|
58
|
+
archive: "아카이브",
|
|
59
|
+
home: "홈",
|
|
60
|
+
categoryNav: "카테고리 내비게이션",
|
|
61
|
+
more: "더 보기",
|
|
62
|
+
totalPosts: "총 {n}편의 글",
|
|
63
|
+
postsInYear: "{n}편",
|
|
64
|
+
yearUnit: "{n}년",
|
|
65
|
+
totalTags: "총 {n}개의 태그",
|
|
66
|
+
tagTitle: "태그: {tag}",
|
|
67
|
+
totalCategories: "총 {n}개의 카테고리",
|
|
68
|
+
categoryTitle: "카테고리: {category}",
|
|
69
|
+
publishedOn: "{date} 게시",
|
|
70
|
+
updatedOn: "{date} 수정",
|
|
71
|
+
pageviews: "조회수",
|
|
72
|
+
notFound: "페이지를 찾을 수 없습니다",
|
|
73
|
+
notFoundDesc: "페이지가 존재하지 않거나 이동되었습니다.",
|
|
74
|
+
backHome: "홈으로 돌아가기",
|
|
75
|
+
about: "소개",
|
|
76
|
+
aboutEmpty: "src/content/spec/about.md에서 이 페이지를 작성하세요",
|
|
77
|
+
avatarAlt: "{name}의 아바타",
|
|
78
|
+
noFriends: "아직 링크가 없습니다",
|
|
79
|
+
applyFriend: "링크 신청",
|
|
80
|
+
/* 애니 시청 페이지 */
|
|
81
|
+
bangumiEmpty: "시청 중인 애니메이션이 없습니다",
|
|
82
|
+
bangumiFail:
|
|
83
|
+
"데이터를 가져오지 못했습니다. 사이트를 다시 빌드한 후 재시도하세요",
|
|
84
|
+
epProgress: "{n} / {total}화 시청",
|
|
85
|
+
epProgressOpen: "{n}화 시청",
|
|
86
|
+
myRate: "내 평점 {n}",
|
|
87
|
+
guestbook: "방명록",
|
|
88
|
+
guestbookSub: "흔적을 남겨 주세요",
|
|
89
|
+
commentComingSoon: "댓글 창이 곧 열릴 예정입니다. 기대해 주세요.",
|
|
90
|
+
qrAlt: "{name}의 QR 코드",
|
|
91
|
+
goSponsor: "후원하기",
|
|
92
|
+
qrPlaceholder: "QR 코드 자리 표시",
|
|
93
|
+
sponsorList: "후원자 명단",
|
|
94
|
+
noSponsors: "아직 후원이 없습니다. 첫 번째 후원자가 되어 보세요",
|
|
95
|
+
search: "검색",
|
|
96
|
+
searchShortcut: "/ 또는 Ctrl+K로 빠른 검색",
|
|
97
|
+
searchLoading: "검색 중…",
|
|
98
|
+
searchNoResults: "관련 결과를 찾을 수 없습니다",
|
|
99
|
+
searchUnavailable: "검색 인덱스는 빌드 후 사용할 수 있습니다",
|
|
100
|
+
searchTypeSomething: "키워드를 입력해 검색하세요",
|
|
101
|
+
searchMore: "전체 {n}개 결과 보기",
|
|
102
|
+
searchResultsTotal: "{n}개 결과",
|
|
103
|
+
galleryPhotos: "사진 {n}장",
|
|
104
|
+
backToAlbums: "앨범으로 돌아가기",
|
|
105
|
+
searchAlbums: "앨범 검색…",
|
|
106
|
+
allTags: "전체",
|
|
107
|
+
galleryNoAlbums:
|
|
108
|
+
"아직 앨범이 없습니다. 테마 설정의 gallery.albums에 추가하세요.",
|
|
109
|
+
galleryNoPhotos: "이 앨범에는 사진이 없습니다",
|
|
110
|
+
/* 짧은 글 페이지 */
|
|
111
|
+
momentsTitle: "일상",
|
|
112
|
+
momentsEmpty: "아직 글이 없습니다.",
|
|
113
|
+
encryptedAlbum: "암호화된 앨범",
|
|
114
|
+
searchAlbumNoResults: "일치하는 앨범이 없습니다",
|
|
115
|
+
postEncrypted: "암호화됨",
|
|
116
|
+
passwordProtected: "비밀번호 보호",
|
|
117
|
+
passwordProtectedDesc:
|
|
118
|
+
"이 내용은 비밀번호로 보호되어 있습니다. 보려면 비밀번호를 입력하세요.",
|
|
119
|
+
passwordHint: "힌트",
|
|
120
|
+
passwordPlaceholder: "비밀번호를 입력하세요",
|
|
121
|
+
passwordSubmit: "잠금 해제",
|
|
122
|
+
passwordError: "비밀번호가 잘못되었습니다. 다시 시도하세요.",
|
|
123
|
+
close: "닫기",
|
|
124
|
+
prevImage: "이전 이미지",
|
|
125
|
+
nextImage: "다음 이미지",
|
|
126
|
+
copied: "복사됨",
|
|
127
|
+
fullscreen: "전체 화면",
|
|
128
|
+
slideshow: "자동 재생",
|
|
129
|
+
thumbs: "썸네일",
|
|
130
|
+
/* 공유 포스터 */
|
|
131
|
+
sharePoster: "공유 포스터 만들기",
|
|
132
|
+
generatingPoster: "포스터 생성 중…",
|
|
133
|
+
copyLink: "링크 복사",
|
|
134
|
+
savePoster: "포스터 저장",
|
|
135
|
+
scanToRead: "QR 코드를 스캔하여 전문 보기",
|
|
136
|
+
author: "저자",
|
|
137
|
+
supportTitle: "응원하기",
|
|
138
|
+
supportDesc: "이 글이 도움이 되었다면 후원이나 공유를 부탁드립니다.",
|
|
139
|
+
sponsorMe: "후원하기",
|
|
140
|
+
lastModifiedPrefix: "마지막 업데이트: ",
|
|
141
|
+
lastModifiedDaysAgo: ", {days}일 경과",
|
|
142
|
+
lastModifiedOutdated: "일부 내용이 오래되었을 수 있으니 유의하세요.",
|
|
143
|
+
/* 시리즈 */
|
|
144
|
+
series: "시리즈",
|
|
145
|
+
seriesTotal: "총 {n}개의 시리즈",
|
|
146
|
+
seriesPostsCount: "{n}편",
|
|
147
|
+
seriesUpdated: "{date} 업데이트",
|
|
148
|
+
seriesEmpty:
|
|
149
|
+
"아직 시리즈가 없습니다. 글 frontmatter에 series를 입력하면 생성됩니다.",
|
|
150
|
+
seriesProgress: "{cur} / {total}",
|
|
151
|
+
seriesCatalog: "시리즈 목차",
|
|
152
|
+
/* 음악 플레이어 */
|
|
153
|
+
musicOpen: "음악 플레이어",
|
|
154
|
+
musicClose: "플레이어 접기",
|
|
155
|
+
musicPlay: "재생",
|
|
156
|
+
musicPause: "일시정지",
|
|
157
|
+
musicPrev: "이전 곡",
|
|
158
|
+
musicNext: "다음 곡",
|
|
159
|
+
musicModeList: "목록 반복",
|
|
160
|
+
musicModeOne: "한 곡 반복",
|
|
161
|
+
musicModeRandom: "랜덤 재생",
|
|
162
|
+
musicLyrics: "가사",
|
|
163
|
+
musicPlaylist: "재생 목록",
|
|
164
|
+
musicNoLyrics: "가사 없음",
|
|
165
|
+
musicEmpty: "재생 목록이 비어 있습니다",
|
|
166
|
+
musicSeek: "재생 진행률",
|
|
167
|
+
musicVolume: "음량",
|
|
168
|
+
l2dHome: "홈으로",
|
|
169
|
+
l2dScrollTop: "맨 위로",
|
|
170
|
+
l2dSleep: "잠시 쉬기",
|
|
171
|
+
l2dSwitchModel: "모델 바꾸기",
|
|
172
|
+
/* 코드 접기 */
|
|
173
|
+
codeFoldExpand: "코드 펼치기",
|
|
174
|
+
codeFoldCollapse: "코드 접기",
|
|
175
|
+
codeFoldExpanded: "코드 블록이 펼쳐졌습니다",
|
|
176
|
+
codeFoldCollapsed: "코드 블록이 접혔습니다",
|
|
177
|
+
/* 배경화면 */
|
|
178
|
+
wpPlay: "배경화면 동영상 재생",
|
|
179
|
+
wpPause: "배경화면 동영상 일시정지",
|
|
180
|
+
wpPrev: "이전 동영상",
|
|
181
|
+
wpNext: "다음 동영상",
|
|
182
|
+
wpMode: "배경화면 모드 전환",
|
|
183
|
+
/* 디스플레이 설정 패널(§4.103):기어 진입 + 세 탭 + 그룹 컨트롤 */
|
|
184
|
+
displaySettings: "디스플레이 설정",
|
|
185
|
+
tabAppearance: "외관",
|
|
186
|
+
tabWallpaper: "배경화면",
|
|
187
|
+
tabEffects: "효과",
|
|
188
|
+
wallpaperMode: "배경화면 모드",
|
|
189
|
+
modeBanner: "배너 배경화면",
|
|
190
|
+
modeFullscreen: "전체 화면 배경화면",
|
|
191
|
+
modeOverlay: "오버레이 투명",
|
|
192
|
+
modeNone: "단색 배경",
|
|
193
|
+
fullscreenLayout: "전체 화면 레이아웃",
|
|
194
|
+
layoutClassic: "클래식 모드",
|
|
195
|
+
layoutHero: "Hero 모드",
|
|
196
|
+
wallpaperSettings: "배경화면 설정",
|
|
197
|
+
homeWallpaperTitle: "홈 배경화면 제목",
|
|
198
|
+
wallpaperCarousel: "배경화면 캐러셀",
|
|
199
|
+
wavesAnimation: "물결 애니메이션",
|
|
200
|
+
gradientTransition: "그라데이션 전환",
|
|
201
|
+
overlaySettings: "투명도 설정",
|
|
202
|
+
wallpaperOpacity: "배경화면 불투명도",
|
|
203
|
+
backgroundBlur: "배경 블러",
|
|
204
|
+
cardOpacity: "카드 불투명도",
|
|
205
|
+
sakuraEffect: "벚꽃 효과",
|
|
206
|
+
/* 사이트 정보 총 글자 수 */
|
|
207
|
+
totalWords: "총 글자 수",
|
|
208
|
+
/* 사이드바 음악 위젯 */
|
|
209
|
+
musicWidget: "음악",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export default ko;
|
package/src/i18n/resolve.ts
CHANGED
|
@@ -8,15 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import en from "./en.js";
|
|
11
|
+
import ja from "./ja.js";
|
|
12
|
+
import ko from "./ko.js";
|
|
13
|
+
import ru from "./ru.js";
|
|
11
14
|
import zhCN, { type UiKey } from "./zh-CN.js";
|
|
15
|
+
import zhTW from "./zh-TW.js";
|
|
12
16
|
|
|
13
17
|
type Pack = Partial<Record<UiKey, string>>;
|
|
14
18
|
|
|
15
|
-
/** 已内置语言包(键为归一化 lang
|
|
19
|
+
/** 已内置语言包(键为归一化 lang:小写、下划线转连字符;ja-jp/ko-kr 等经基础语言回退命中) */
|
|
16
20
|
const packs: Record<string, Pack> = {
|
|
17
21
|
en: en,
|
|
18
22
|
"en-us": en,
|
|
19
23
|
"en-gb": en,
|
|
24
|
+
"zh-tw": zhTW,
|
|
25
|
+
"zh-hant": zhTW,
|
|
26
|
+
ja: ja,
|
|
27
|
+
ko: ko,
|
|
28
|
+
ru: ru,
|
|
20
29
|
};
|
|
21
30
|
|
|
22
31
|
function norm(lang: string): string {
|