valaxy 0.28.0-beta.6 → 0.28.0
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/client/components/.exclude/ValaxyDebug.vue +176 -0
- package/client/components/ValaxyOpenInEditor.vue +53 -0
- package/client/composables/app/useValaxyApp.ts +1 -7
- package/client/composables/categories.ts +3 -56
- package/client/composables/category-utils.ts +50 -0
- package/client/composables/index.ts +1 -0
- package/client/composables/locale.ts +68 -9
- package/client/composables/outline/anchor.ts +1 -1
- package/client/composables/post/index.ts +6 -3
- package/client/composables/tags.ts +0 -5
- package/client/modules/components.ts +7 -0
- package/client/modules/valaxy.ts +28 -3
- package/dist/node/cli/index.mjs +1 -1
- package/dist/node/index.d.mts +31 -1
- package/dist/node/index.mjs +1 -1
- package/dist/shared/{valaxy.JIuR8V4d.d.mts → valaxy.6MW2qn5T.d.mts} +12 -4
- package/dist/shared/{valaxy.BVsZMcdc.mjs → valaxy.DQ6HsU2J.mjs} +317 -15
- package/dist/types/index.d.mts +2 -2
- package/package.json +11 -12
- package/shared/node/i18n.ts +4 -4
- package/shared/utils/i18n.ts +55 -0
- package/types/config.ts +0 -4
- package/types/frontmatter/page.ts +12 -0
package/dist/node/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ViteSSGOptions } from 'vite-ssg';
|
|
2
2
|
import * as vite from 'vite';
|
|
3
3
|
import { UserConfig, InlineConfig, ViteDevServer, PluginOption, Plugin } from 'vite';
|
|
4
|
-
import { D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon, S as SiteConfig, U as UserSiteConfig } from '../shared/valaxy.
|
|
4
|
+
import { D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon, S as SiteConfig, U as UserSiteConfig } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
5
5
|
import Vue from '@vitejs/plugin-vue';
|
|
6
6
|
import { Hookable } from 'hookable';
|
|
7
7
|
import { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
|
@@ -694,6 +694,30 @@ interface ValaxyExtendConfig {
|
|
|
694
694
|
*/
|
|
695
695
|
maxDuration?: number;
|
|
696
696
|
};
|
|
697
|
+
/**
|
|
698
|
+
* @en Taxonomy i18n validation during `valaxy dev` / `valaxy build`.
|
|
699
|
+
* Checks whether translated `tag.*` / `category.*` keys are consistently
|
|
700
|
+
* defined across configured languages.
|
|
701
|
+
*
|
|
702
|
+
* @zh `valaxy dev` / `valaxy build` 期间的 taxonomy i18n 校验。
|
|
703
|
+
* 用于检查 `tag.*` / `category.*` 翻译 key 是否在已配置语言中保持一致。
|
|
704
|
+
*/
|
|
705
|
+
taxonomyI18n?: {
|
|
706
|
+
/**
|
|
707
|
+
* @en Validation level for taxonomy i18n checks.
|
|
708
|
+
* - `'off'`: disable checks
|
|
709
|
+
* - `'warn'`: print warnings and continue
|
|
710
|
+
* - `'error'`: fail validation after reporting all issues
|
|
711
|
+
*
|
|
712
|
+
* @zh taxonomy i18n 校验级别。
|
|
713
|
+
* - `'off'`:关闭检查
|
|
714
|
+
* - `'warn'`:输出 warning 并继续流程
|
|
715
|
+
* - `'error'`:输出所有问题后以错误结束
|
|
716
|
+
*
|
|
717
|
+
* @default 'warn'
|
|
718
|
+
*/
|
|
719
|
+
level?: 'off' | 'warn' | 'error';
|
|
720
|
+
};
|
|
697
721
|
};
|
|
698
722
|
/**
|
|
699
723
|
* @experimental
|
|
@@ -744,6 +768,12 @@ interface ValaxyExtendConfig {
|
|
|
744
768
|
* @default true
|
|
745
769
|
*/
|
|
746
770
|
katex: boolean;
|
|
771
|
+
/**
|
|
772
|
+
* @description:en-US Auto-extract the first image from markdown content for Open Graph fallback
|
|
773
|
+
* @description:zh-CN 自动从 Markdown 内容中提取第一张图片,作为 Open Graph 的回退图片
|
|
774
|
+
* @default true
|
|
775
|
+
*/
|
|
776
|
+
extractFirstImage: boolean;
|
|
747
777
|
};
|
|
748
778
|
/**
|
|
749
779
|
* Enable MathJax3 math rendering (aligned with VitePress `markdown.math`).
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.
|
|
1
|
+
export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.DQ6HsU2J.mjs';
|
|
2
2
|
import 'node:path';
|
|
3
3
|
import 'fs-extra';
|
|
4
4
|
import 'consola/utils';
|
|
@@ -167,6 +167,18 @@ interface PageFrontMatter extends BaseFrontMatter {
|
|
|
167
167
|
* @description 封面图片
|
|
168
168
|
*/
|
|
169
169
|
cover: string;
|
|
170
|
+
/**
|
|
171
|
+
* @description:en-US Open Graph image for SEO
|
|
172
|
+
* @description:zh-CN Open Graph 图片,用于 SEO
|
|
173
|
+
*/
|
|
174
|
+
ogImage: string;
|
|
175
|
+
/**
|
|
176
|
+
* @protected
|
|
177
|
+
* @tutorial ⚠️ DO NOT SET MANUALLY (auto-extracted from markdown content)
|
|
178
|
+
* @description:en-US First image URL extracted from markdown content
|
|
179
|
+
* @description:zh-CN 从 Markdown 内容中自动提取的第一张图片 URL
|
|
180
|
+
*/
|
|
181
|
+
firstImage: string;
|
|
170
182
|
/**
|
|
171
183
|
* display toc
|
|
172
184
|
* @description 是否显示目录
|
|
@@ -638,10 +650,6 @@ interface SiteConfig {
|
|
|
638
650
|
* @zh 是否启用
|
|
639
651
|
*/
|
|
640
652
|
enable: boolean;
|
|
641
|
-
/**
|
|
642
|
-
* @deprecated will be deprecated, use search.provider instead
|
|
643
|
-
*/
|
|
644
|
-
type?: SiteConfig['search']['provider'];
|
|
645
653
|
/**
|
|
646
654
|
* Search Type
|
|
647
655
|
* - algolia: Algolia Search
|
|
@@ -28,7 +28,7 @@ import { Feed } from 'feed';
|
|
|
28
28
|
import MarkdownIt from 'markdown-it';
|
|
29
29
|
import { table, getBorderCharacters } from 'table';
|
|
30
30
|
import { createHooks } from 'hookable';
|
|
31
|
-
import { execFileSync, exec } from 'node:child_process';
|
|
31
|
+
import { execFileSync, execSync, exec } from 'node:child_process';
|
|
32
32
|
import v8 from 'node:v8';
|
|
33
33
|
import generateSitemap from 'vite-ssg-sitemap';
|
|
34
34
|
import { createMarkdownItAsync, MarkdownItAsync } from 'markdown-it-async';
|
|
@@ -854,6 +854,9 @@ const defaultValaxyConfig = {
|
|
|
854
854
|
foucGuard: {
|
|
855
855
|
enabled: true,
|
|
856
856
|
maxDuration: 5e3
|
|
857
|
+
},
|
|
858
|
+
taxonomyI18n: {
|
|
859
|
+
level: "warn"
|
|
857
860
|
}
|
|
858
861
|
},
|
|
859
862
|
deploy: {},
|
|
@@ -875,7 +878,8 @@ const defaultValaxyConfig = {
|
|
|
875
878
|
}
|
|
876
879
|
},
|
|
877
880
|
features: {
|
|
878
|
-
katex: true
|
|
881
|
+
katex: true,
|
|
882
|
+
extractFirstImage: true
|
|
879
883
|
},
|
|
880
884
|
math: false,
|
|
881
885
|
cdn: {
|
|
@@ -928,8 +932,9 @@ async function resolveValaxyConfigFromRoot(root, options) {
|
|
|
928
932
|
}
|
|
929
933
|
const mergeValaxyConfig = createDefu((obj, key, value) => {
|
|
930
934
|
if (isFunction(obj[key]) && isFunction(value)) {
|
|
935
|
+
const original = obj[key];
|
|
931
936
|
obj[key] = function(...args) {
|
|
932
|
-
|
|
937
|
+
original.call(this, ...args);
|
|
933
938
|
value.call(this, ...args);
|
|
934
939
|
};
|
|
935
940
|
return true;
|
|
@@ -985,6 +990,30 @@ function tObject(data, lang) {
|
|
|
985
990
|
}
|
|
986
991
|
return data;
|
|
987
992
|
}
|
|
993
|
+
function isLocaleKey(value) {
|
|
994
|
+
return value.startsWith(LOCALE_PREFIX);
|
|
995
|
+
}
|
|
996
|
+
function stripLocalePrefix(value) {
|
|
997
|
+
return isLocaleKey(value) ? value.slice(LOCALE_PREFIX.length) : value;
|
|
998
|
+
}
|
|
999
|
+
function getLocaleMessageValue(messages, key) {
|
|
1000
|
+
return key.split(".").reduce((result, part) => result?.[part], messages);
|
|
1001
|
+
}
|
|
1002
|
+
function hasLocaleMessage(messages, key) {
|
|
1003
|
+
return getLocaleMessageValue(messages, key) !== void 0;
|
|
1004
|
+
}
|
|
1005
|
+
function resolveTaxonomyLocaleKey(namespace, rawValue) {
|
|
1006
|
+
if (isLocaleKey(rawValue)) {
|
|
1007
|
+
return {
|
|
1008
|
+
localeKey: stripLocalePrefix(rawValue),
|
|
1009
|
+
isExplicitLocaleKey: true
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
localeKey: `${namespace}.${rawValue}`,
|
|
1014
|
+
isExplicitLocaleKey: false
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
988
1017
|
|
|
989
1018
|
const indexRE = /(^|.*\/)index.md(.*)$/i;
|
|
990
1019
|
function linkPlugin(md, externalAttrs, base) {
|
|
@@ -1692,7 +1721,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
|
|
|
1692
1721
|
return md;
|
|
1693
1722
|
}
|
|
1694
1723
|
|
|
1695
|
-
const version = "0.28.0
|
|
1724
|
+
const version = "0.28.0";
|
|
1696
1725
|
|
|
1697
1726
|
const GLOBAL_STATE = {
|
|
1698
1727
|
valaxyApp: void 0,
|
|
@@ -2482,9 +2511,8 @@ function loadLocalesYml(localesPath, force = false) {
|
|
|
2482
2511
|
return locales;
|
|
2483
2512
|
}
|
|
2484
2513
|
function nodeT(key, lang) {
|
|
2485
|
-
if (key
|
|
2486
|
-
key = key
|
|
2487
|
-
}
|
|
2514
|
+
if (isLocaleKey(key))
|
|
2515
|
+
key = stripLocalePrefix(key);
|
|
2488
2516
|
const data = NODE_I18N.locales[lang] || {};
|
|
2489
2517
|
const keys = key.split(".");
|
|
2490
2518
|
let result = data;
|
|
@@ -2529,7 +2557,7 @@ function getSiteUrl(options) {
|
|
|
2529
2557
|
}
|
|
2530
2558
|
|
|
2531
2559
|
function resolveText(value, lang) {
|
|
2532
|
-
if (typeof value === "string" && value
|
|
2560
|
+
if (typeof value === "string" && isLocaleKey(value))
|
|
2533
2561
|
return nodeT(value, lang);
|
|
2534
2562
|
return tObject(value, lang);
|
|
2535
2563
|
}
|
|
@@ -3368,10 +3396,24 @@ function inferDescription(frontmatter) {
|
|
|
3368
3396
|
return description;
|
|
3369
3397
|
return head && getHeadMetaContent(head, "description") || "";
|
|
3370
3398
|
}
|
|
3399
|
+
function extractFirstImage(code) {
|
|
3400
|
+
const mdImageMatch = code.match(/!\[.*?\]\((.+?)\)/);
|
|
3401
|
+
if (mdImageMatch)
|
|
3402
|
+
return mdImageMatch[1];
|
|
3403
|
+
const htmlImageMatch = code.match(/<img\s[^>]*?src=["'](.+?)["']/);
|
|
3404
|
+
if (htmlImageMatch)
|
|
3405
|
+
return htmlImageMatch[1];
|
|
3406
|
+
return void 0;
|
|
3407
|
+
}
|
|
3371
3408
|
async function generatePageData(code, id, options) {
|
|
3372
3409
|
const fileInfo = Valaxy.state.idMap.get(id);
|
|
3373
3410
|
const relativePath = path.relative(options.userRoot, id);
|
|
3374
3411
|
const fm = JSON.parse(JSON.stringify(fileInfo?.frontmatter));
|
|
3412
|
+
if (options.config.features?.extractFirstImage !== false && !fm.ogImage && !fm.cover) {
|
|
3413
|
+
const firstImage = extractFirstImage(code);
|
|
3414
|
+
if (firstImage)
|
|
3415
|
+
fm.firstImage = firstImage;
|
|
3416
|
+
}
|
|
3375
3417
|
const pageData = {
|
|
3376
3418
|
title: fm.title || fileInfo?.title || "",
|
|
3377
3419
|
titleTemplate: fm.titleTemplate,
|
|
@@ -3382,7 +3424,8 @@ async function generatePageData(code, id, options) {
|
|
|
3382
3424
|
relativePath,
|
|
3383
3425
|
filePath: id
|
|
3384
3426
|
};
|
|
3385
|
-
|
|
3427
|
+
if (options.config.siteConfig.lastUpdated)
|
|
3428
|
+
pageData.lastUpdated = await getGitTimestamp(id);
|
|
3386
3429
|
return pageData;
|
|
3387
3430
|
}
|
|
3388
3431
|
|
|
@@ -5375,17 +5418,21 @@ async function getPosts(params, options) {
|
|
|
5375
5418
|
return { data, content, excerpt, path: i };
|
|
5376
5419
|
});
|
|
5377
5420
|
const rawPosts = await Promise.all(readFilePromises);
|
|
5421
|
+
const draftPosts = [];
|
|
5378
5422
|
const filteredPosts = rawPosts.filter((p) => {
|
|
5379
5423
|
const { data } = p;
|
|
5380
5424
|
if (data.password)
|
|
5381
5425
|
return false;
|
|
5382
5426
|
if (data.draft) {
|
|
5427
|
+
draftPosts.push(p.path);
|
|
5383
5428
|
return false;
|
|
5384
5429
|
}
|
|
5385
5430
|
if (data.hide)
|
|
5386
5431
|
return false;
|
|
5387
5432
|
return true;
|
|
5388
5433
|
});
|
|
5434
|
+
if (draftPosts.length)
|
|
5435
|
+
consola.log(`[rss] Skipped ${draftPosts.length} draft post(s): ${draftPosts.join(", ")}`);
|
|
5389
5436
|
const posts = [];
|
|
5390
5437
|
for (const rawPost of filteredPosts) {
|
|
5391
5438
|
const { data, path, content, excerpt } = rawPost;
|
|
@@ -5650,6 +5697,162 @@ const content = {
|
|
|
5650
5697
|
loadAllContent: loadAllContent
|
|
5651
5698
|
};
|
|
5652
5699
|
|
|
5700
|
+
function getConfiguredLanguages(options) {
|
|
5701
|
+
const configured = options.config.siteConfig.languages?.length ? options.config.siteConfig.languages : [options.config.siteConfig.lang || "en"];
|
|
5702
|
+
return [...new Set(configured.filter(Boolean))];
|
|
5703
|
+
}
|
|
5704
|
+
function normalizeToStringArray(value) {
|
|
5705
|
+
if (typeof value === "string")
|
|
5706
|
+
return value.trim() ? [value] : [];
|
|
5707
|
+
if (Array.isArray(value)) {
|
|
5708
|
+
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
5709
|
+
}
|
|
5710
|
+
return [];
|
|
5711
|
+
}
|
|
5712
|
+
async function collectPageFiles(options) {
|
|
5713
|
+
const userPages = await scanPageFiles(options.userRoot, ["**/*.md"]);
|
|
5714
|
+
const userPageFiles = userPages.map((file) => resolve(options.userRoot, "pages", file));
|
|
5715
|
+
const contentDir = resolve(options.tempDir, "content", "pages");
|
|
5716
|
+
if (!await fs.pathExists(contentDir))
|
|
5717
|
+
return userPageFiles;
|
|
5718
|
+
const contentPages = await fg(["**/*.md"], {
|
|
5719
|
+
cwd: contentDir,
|
|
5720
|
+
ignore: ["**/node_modules"]
|
|
5721
|
+
});
|
|
5722
|
+
return [.../* @__PURE__ */ new Set([
|
|
5723
|
+
...userPageFiles,
|
|
5724
|
+
...contentPages.map((file) => resolve(contentDir, file))
|
|
5725
|
+
])];
|
|
5726
|
+
}
|
|
5727
|
+
async function loadMergedLocaleMessages(options) {
|
|
5728
|
+
const languages = getConfiguredLanguages(options);
|
|
5729
|
+
const messages = Object.fromEntries(languages.map((lang) => [lang, {}]));
|
|
5730
|
+
for (const root of options.roots) {
|
|
5731
|
+
for (const lang of languages) {
|
|
5732
|
+
const localeFiles = [
|
|
5733
|
+
resolve(root, "locales", `${lang}.yml`),
|
|
5734
|
+
resolve(root, "locales", `${lang}.yaml`)
|
|
5735
|
+
];
|
|
5736
|
+
for (const localeFile of localeFiles) {
|
|
5737
|
+
if (!await fs.pathExists(localeFile))
|
|
5738
|
+
continue;
|
|
5739
|
+
const content = await fs.readFile(localeFile, "utf-8");
|
|
5740
|
+
if (!content.trim())
|
|
5741
|
+
continue;
|
|
5742
|
+
const data = yaml.load(content);
|
|
5743
|
+
messages[lang] = replaceArrMerge(data || {}, messages[lang]);
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
return messages;
|
|
5748
|
+
}
|
|
5749
|
+
async function collectTaxonomyUsages(options) {
|
|
5750
|
+
const files = await collectPageFiles(options);
|
|
5751
|
+
const posts = await readPostFiles(files);
|
|
5752
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
5753
|
+
const addUsage = (namespace, rawValue, file) => {
|
|
5754
|
+
const key = `${namespace}:${rawValue}`;
|
|
5755
|
+
if (!usageMap.has(key)) {
|
|
5756
|
+
usageMap.set(key, {
|
|
5757
|
+
namespace,
|
|
5758
|
+
rawValue,
|
|
5759
|
+
files: /* @__PURE__ */ new Set()
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
usageMap.get(key).files.add(file);
|
|
5763
|
+
};
|
|
5764
|
+
for (const post of posts) {
|
|
5765
|
+
const file = relative(options.userRoot, post.filePath);
|
|
5766
|
+
for (const tag of normalizeToStringArray(post.data.tags))
|
|
5767
|
+
addUsage("tag", tag, file);
|
|
5768
|
+
for (const category of normalizeToStringArray(post.data.categories))
|
|
5769
|
+
addUsage("category", category, file);
|
|
5770
|
+
}
|
|
5771
|
+
return Array.from(usageMap.values(), (item) => ({
|
|
5772
|
+
namespace: item.namespace,
|
|
5773
|
+
rawValue: item.rawValue,
|
|
5774
|
+
files: [...item.files].sort()
|
|
5775
|
+
})).sort((a, b) => `${a.namespace}:${a.rawValue}`.localeCompare(`${b.namespace}:${b.rawValue}`));
|
|
5776
|
+
}
|
|
5777
|
+
function findTaxonomyI18nIssues(usages, messages, languages) {
|
|
5778
|
+
const issues = [];
|
|
5779
|
+
for (const usage of usages) {
|
|
5780
|
+
const { namespace, rawValue, files } = usage;
|
|
5781
|
+
const { localeKey, isExplicitLocaleKey } = resolveTaxonomyLocaleKey(namespace, rawValue);
|
|
5782
|
+
const presentLanguages = languages.filter((lang) => hasLocaleMessage(messages[lang], localeKey));
|
|
5783
|
+
const missingLanguages = languages.filter((lang) => !hasLocaleMessage(messages[lang], localeKey));
|
|
5784
|
+
if (isExplicitLocaleKey) {
|
|
5785
|
+
if (missingLanguages.length) {
|
|
5786
|
+
issues.push({
|
|
5787
|
+
type: "explicit-locale-key-missing",
|
|
5788
|
+
namespace,
|
|
5789
|
+
rawValue,
|
|
5790
|
+
localeKey,
|
|
5791
|
+
presentLanguages,
|
|
5792
|
+
missingLanguages,
|
|
5793
|
+
files
|
|
5794
|
+
});
|
|
5795
|
+
}
|
|
5796
|
+
continue;
|
|
5797
|
+
}
|
|
5798
|
+
if (presentLanguages.length === 0 || presentLanguages.length === languages.length)
|
|
5799
|
+
continue;
|
|
5800
|
+
issues.push({
|
|
5801
|
+
type: "partial-locale-coverage",
|
|
5802
|
+
namespace,
|
|
5803
|
+
rawValue,
|
|
5804
|
+
localeKey,
|
|
5805
|
+
presentLanguages,
|
|
5806
|
+
missingLanguages,
|
|
5807
|
+
files
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
return issues;
|
|
5811
|
+
}
|
|
5812
|
+
function formatFiles(files) {
|
|
5813
|
+
const preview = files.slice(0, 3).map((file) => colors.dim(file)).join(", ");
|
|
5814
|
+
if (files.length <= 3)
|
|
5815
|
+
return preview;
|
|
5816
|
+
return `${preview}${colors.dim(` and ${files.length - 3} more`)}`;
|
|
5817
|
+
}
|
|
5818
|
+
function logTaxonomyI18nIssues(issues, level) {
|
|
5819
|
+
if (!issues.length)
|
|
5820
|
+
return;
|
|
5821
|
+
const log = level === "error" ? consola.error : consola.warn;
|
|
5822
|
+
const count = level === "error" ? colors.red(String(issues.length)) : colors.yellow(String(issues.length));
|
|
5823
|
+
log(`Detected ${count} taxonomy i18n issue(s).`);
|
|
5824
|
+
for (const issue of issues) {
|
|
5825
|
+
const where = formatFiles(issue.files);
|
|
5826
|
+
if (issue.type === "explicit-locale-key-missing") {
|
|
5827
|
+
log(
|
|
5828
|
+
`[taxonomy-i18n] ${colors.cyan(issue.rawValue)} is an explicit locale key but ${colors.red(`missing in: ${issue.missingLanguages.join(", ")}`)}. Files: ${where}`
|
|
5829
|
+
);
|
|
5830
|
+
continue;
|
|
5831
|
+
}
|
|
5832
|
+
log(
|
|
5833
|
+
`[taxonomy-i18n] ${colors.cyan(issue.rawValue)} resolves to ${colors.cyan(issue.localeKey)} in ${colors.green(issue.presentLanguages.join(", "))}, but is ${colors.red(`missing in: ${issue.missingLanguages.join(", ")}`)}. Files: ${where}`
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
function resolveTaxonomyI18nValidationLevel(options) {
|
|
5838
|
+
return options.config.build.taxonomyI18n?.level || "warn";
|
|
5839
|
+
}
|
|
5840
|
+
async function validateTaxonomyI18n(options) {
|
|
5841
|
+
const level = resolveTaxonomyI18nValidationLevel(options);
|
|
5842
|
+
if (level === "off")
|
|
5843
|
+
return [];
|
|
5844
|
+
const languages = getConfiguredLanguages(options);
|
|
5845
|
+
const [messages, usages] = await Promise.all([
|
|
5846
|
+
loadMergedLocaleMessages(options),
|
|
5847
|
+
collectTaxonomyUsages(options)
|
|
5848
|
+
]);
|
|
5849
|
+
const issues = findTaxonomyI18nIssues(usages, messages, languages);
|
|
5850
|
+
logTaxonomyI18nIssues(issues, level);
|
|
5851
|
+
if (level === "error" && issues.length)
|
|
5852
|
+
throw new Error(`Taxonomy i18n validation failed with ${issues.length} issue(s).`);
|
|
5853
|
+
return issues;
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5653
5856
|
function getServerInfoText(msg) {
|
|
5654
5857
|
return `${valaxyPrefix} ${colors.gray(msg)}`;
|
|
5655
5858
|
}
|
|
@@ -5790,6 +5993,7 @@ async function execBuild({ ssg, ssgEngine, root, output, log }) {
|
|
|
5790
5993
|
);
|
|
5791
5994
|
await callHookWithLog("config:init", valaxyApp);
|
|
5792
5995
|
await callHookWithLog("build:before", valaxyApp);
|
|
5996
|
+
await validateTaxonomyI18n(options);
|
|
5793
5997
|
consola.box("\u{1F320} Start building...");
|
|
5794
5998
|
try {
|
|
5795
5999
|
if (ssg) {
|
|
@@ -5897,14 +6101,108 @@ function registerCleanCommand(cli) {
|
|
|
5897
6101
|
);
|
|
5898
6102
|
}
|
|
5899
6103
|
|
|
5900
|
-
function
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
6104
|
+
function getPnpmVersion() {
|
|
6105
|
+
try {
|
|
6106
|
+
return execSync("pnpm --version", { encoding: "utf-8" }).trim();
|
|
6107
|
+
} catch {
|
|
6108
|
+
return "not found";
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
async function collectDebugInfo() {
|
|
6112
|
+
const info = {
|
|
6113
|
+
os: os.platform(),
|
|
6114
|
+
arch: os.arch(),
|
|
6115
|
+
node: process.version,
|
|
6116
|
+
pnpm: getPnpmVersion(),
|
|
6117
|
+
valaxy: version
|
|
6118
|
+
};
|
|
6119
|
+
try {
|
|
6120
|
+
const options = await resolveOptions({ userRoot: process.cwd() });
|
|
6121
|
+
info.userRoot = options.userRoot;
|
|
6122
|
+
info.theme = options.theme;
|
|
6123
|
+
info.themeVersion = options.config.themeConfig?.pkg?.version;
|
|
6124
|
+
info.addons = options.addons.filter((a) => a.enable).map((a) => ({
|
|
6125
|
+
name: a.name,
|
|
6126
|
+
version: a.pkg?.version || "unknown",
|
|
6127
|
+
global: a.global
|
|
6128
|
+
}));
|
|
6129
|
+
info.pages = options.pages.length;
|
|
6130
|
+
} catch {
|
|
6131
|
+
}
|
|
6132
|
+
return info;
|
|
6133
|
+
}
|
|
6134
|
+
function printFancy(info) {
|
|
6135
|
+
const lines = [];
|
|
6136
|
+
lines.push(`${colors.bold(colors.cyan("Environment"))}`);
|
|
6137
|
+
lines.push(` OS: ${colors.green(`${info.os} ${info.arch}`)}`);
|
|
6138
|
+
lines.push(` Node.js: ${colors.green(info.node)}`);
|
|
6139
|
+
lines.push(` Package Manager: ${colors.green(`pnpm ${info.pnpm}`)}`);
|
|
6140
|
+
lines.push(` Valaxy: ${colors.cyan(`v${info.valaxy}`)}`);
|
|
6141
|
+
if (info.theme) {
|
|
6142
|
+
lines.push("");
|
|
6143
|
+
lines.push(`${colors.bold(colors.cyan("Project"))}`);
|
|
6144
|
+
lines.push(` Root: ${colors.dim(info.userRoot)}`);
|
|
6145
|
+
lines.push(` Theme: ${colors.green(info.theme)} ${colors.blue(`v${info.themeVersion || "unknown"}`)}`);
|
|
6146
|
+
if (info.addons && info.addons.length > 0) {
|
|
6147
|
+
lines.push(` Addons:`);
|
|
6148
|
+
info.addons.forEach((addon, i) => {
|
|
6149
|
+
const prefix = i === info.addons.length - 1 ? "\u2514\u2500" : "\u251C\u2500";
|
|
6150
|
+
const globalTag = addon.global ? colors.cyan(" (global)") : "";
|
|
6151
|
+
lines.push(` ${prefix} ${colors.yellow(addon.name)} ${colors.blue(`v${addon.version}`)}${globalTag}`);
|
|
6152
|
+
});
|
|
6153
|
+
} else {
|
|
6154
|
+
lines.push(` Addons: ${colors.dim("none")}`);
|
|
6155
|
+
}
|
|
6156
|
+
lines.push(` Pages: ${colors.green(String(info.pages))}`);
|
|
6157
|
+
}
|
|
6158
|
+
consola.box({
|
|
6159
|
+
title: "\u{1F30C} Valaxy Debug Info",
|
|
6160
|
+
message: lines.join("\n"),
|
|
6161
|
+
style: {
|
|
6162
|
+
borderColor: "cyan"
|
|
6163
|
+
}
|
|
5906
6164
|
});
|
|
5907
6165
|
}
|
|
6166
|
+
function printPlain(info) {
|
|
6167
|
+
const lines = [];
|
|
6168
|
+
lines.push("## Environment");
|
|
6169
|
+
lines.push(`- OS: ${info.os} ${info.arch}`);
|
|
6170
|
+
lines.push(`- Node: ${info.node}`);
|
|
6171
|
+
lines.push(`- Package Manager: pnpm ${info.pnpm}`);
|
|
6172
|
+
lines.push(`- Valaxy: v${info.valaxy}`);
|
|
6173
|
+
if (info.theme) {
|
|
6174
|
+
lines.push("");
|
|
6175
|
+
lines.push("## Project");
|
|
6176
|
+
lines.push(`- Root: ${info.userRoot}`);
|
|
6177
|
+
lines.push(`- Theme: ${info.theme} (v${info.themeVersion || "unknown"})`);
|
|
6178
|
+
if (info.addons && info.addons.length > 0) {
|
|
6179
|
+
const addonStr = info.addons.map((a) => `${a.name} (v${a.version})${a.global ? " [global]" : ""}`).join(", ");
|
|
6180
|
+
lines.push(`- Addons: ${addonStr}`);
|
|
6181
|
+
} else {
|
|
6182
|
+
lines.push("- Addons: none");
|
|
6183
|
+
}
|
|
6184
|
+
lines.push(`- Pages: ${info.pages}`);
|
|
6185
|
+
}
|
|
6186
|
+
console.log(lines.join("\n"));
|
|
6187
|
+
}
|
|
6188
|
+
function registerDebugCommand(cli) {
|
|
6189
|
+
cli.command(
|
|
6190
|
+
"debug",
|
|
6191
|
+
"Display debug information for your Valaxy project",
|
|
6192
|
+
(args) => args.option("plain", {
|
|
6193
|
+
type: "boolean",
|
|
6194
|
+
default: false,
|
|
6195
|
+
describe: "Output plain text without colors (for pasting into issues)"
|
|
6196
|
+
}),
|
|
6197
|
+
async (args) => {
|
|
6198
|
+
const info = await collectDebugInfo();
|
|
6199
|
+
if (args.plain)
|
|
6200
|
+
printPlain(info);
|
|
6201
|
+
else
|
|
6202
|
+
printFancy(info);
|
|
6203
|
+
}
|
|
6204
|
+
);
|
|
6205
|
+
}
|
|
5908
6206
|
|
|
5909
6207
|
function registerDeployCommand(cli) {
|
|
5910
6208
|
cli.command("deploy", "deploy your blog to the cloud", async () => {
|
|
@@ -6075,6 +6373,7 @@ async function startValaxyDev({
|
|
|
6075
6373
|
await valaxyApp.hooks.callHook("content:before-load");
|
|
6076
6374
|
await loadAllContent(loaders, ctx);
|
|
6077
6375
|
await valaxyApp.hooks.callHook("content:loaded");
|
|
6376
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6078
6377
|
for (const loader of loaders) {
|
|
6079
6378
|
if (loader.devPollInterval) {
|
|
6080
6379
|
const poll = async () => {
|
|
@@ -6082,6 +6381,7 @@ async function startValaxyDev({
|
|
|
6082
6381
|
await valaxyApp.hooks.callHook("content:before-load");
|
|
6083
6382
|
await loadAllContent([loader], ctx);
|
|
6084
6383
|
await valaxyApp.hooks.callHook("content:loaded");
|
|
6384
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6085
6385
|
} catch (error) {
|
|
6086
6386
|
consola.error("[content-loader] Error while polling:", error);
|
|
6087
6387
|
} finally {
|
|
@@ -6092,6 +6392,8 @@ async function startValaxyDev({
|
|
|
6092
6392
|
setTimeout(poll, loader.devPollInterval);
|
|
6093
6393
|
}
|
|
6094
6394
|
}
|
|
6395
|
+
} else {
|
|
6396
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6095
6397
|
}
|
|
6096
6398
|
const viteConfig = mergeConfig({
|
|
6097
6399
|
// initial vite config
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as Post } from '../shared/valaxy.
|
|
2
|
-
export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.
|
|
1
|
+
import { c as Post } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
2
|
+
export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
3
3
|
import { Header } from '@valaxyjs/utils';
|
|
4
4
|
import '@vueuse/integrations/useFuse';
|
|
5
5
|
import 'medium-zoom';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "valaxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.28.0
|
|
4
|
+
"version": "0.28.0",
|
|
5
5
|
"description": "📄 Vite & Vue powered static blog generator.",
|
|
6
6
|
"author": {
|
|
7
7
|
"email": "me@yunyoujun.cn",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"types"
|
|
58
58
|
],
|
|
59
59
|
"engines": {
|
|
60
|
-
"node": "^
|
|
60
|
+
"node": "^18.0.0 || >=20.0.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@antfu/install-pkg": "^1.1.0",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"jiti": "^2.6.1",
|
|
96
96
|
"js-base64": "^3.7.8",
|
|
97
97
|
"js-yaml": "^4.1.1",
|
|
98
|
-
"katex": "^0.16.
|
|
98
|
+
"katex": "^0.16.42",
|
|
99
99
|
"lru-cache": "^11.2.7",
|
|
100
100
|
"markdown-it": "^14.1.1",
|
|
101
101
|
"markdown-it-anchor": "^9.2.0",
|
|
@@ -105,12 +105,12 @@
|
|
|
105
105
|
"markdown-it-emoji": "^3.0.0",
|
|
106
106
|
"markdown-it-footnote": "^4.0.0",
|
|
107
107
|
"markdown-it-image-figures": "^2.1.1",
|
|
108
|
-
"markdown-it-table-of-contents": "^1.
|
|
108
|
+
"markdown-it-table-of-contents": "^1.2.0",
|
|
109
109
|
"markdown-it-task-lists": "^2.1.1",
|
|
110
110
|
"medium-zoom": "^1.1.0",
|
|
111
111
|
"mermaid": "^11.13.0",
|
|
112
112
|
"minisearch": "^7.2.0",
|
|
113
|
-
"mlly": "^1.8.
|
|
113
|
+
"mlly": "^1.8.2",
|
|
114
114
|
"nprogress": "^0.2.0",
|
|
115
115
|
"open": "10.1.0",
|
|
116
116
|
"ora": "^9.3.0",
|
|
@@ -129,19 +129,18 @@
|
|
|
129
129
|
"unplugin-vue-components": "28.0.0",
|
|
130
130
|
"unplugin-vue-markdown": "^30.0.0",
|
|
131
131
|
"vanilla-lazyload": "^19.1.3",
|
|
132
|
-
"vite": "^8.0.
|
|
133
|
-
"vite-
|
|
134
|
-
"vite-plugin-vue-
|
|
135
|
-
"vite-plugin-vue-layouts-next": "^2.0.1",
|
|
132
|
+
"vite": "^8.0.2",
|
|
133
|
+
"vite-plugin-vue-devtools": "^8.1.1",
|
|
134
|
+
"vite-plugin-vue-layouts-next": "^2.1.0",
|
|
136
135
|
"vite-ssg": "^28.3.0",
|
|
137
136
|
"vite-ssg-sitemap": "^0.10.0",
|
|
138
137
|
"vitepress-plugin-group-icons": "^1.7.1",
|
|
139
138
|
"vue": "3.5.22",
|
|
140
139
|
"vue-i18n": "^11.3.0",
|
|
141
|
-
"vue-router": "^5.0.
|
|
140
|
+
"vue-router": "^5.0.4",
|
|
142
141
|
"yargs": "^18.0.0",
|
|
143
|
-
"@valaxyjs/devtools": "0.28.0
|
|
144
|
-
"@valaxyjs/utils": "0.28.0
|
|
142
|
+
"@valaxyjs/devtools": "0.28.0",
|
|
143
|
+
"@valaxyjs/utils": "0.28.0"
|
|
145
144
|
},
|
|
146
145
|
"devDependencies": {
|
|
147
146
|
"@mdit-vue/plugin-component": "^3.0.2",
|
package/shared/node/i18n.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra'
|
|
2
2
|
import yaml from 'js-yaml'
|
|
3
|
-
import {
|
|
3
|
+
import { isLocaleKey, stripLocalePrefix } from '../utils/i18n'
|
|
4
4
|
|
|
5
5
|
export const NODE_I18N: {
|
|
6
6
|
locales: Record<string, any>
|
|
@@ -64,9 +64,9 @@ export function loadLocalesYml(localesPath: string, force = false): Record<strin
|
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
66
|
export function nodeT(key: string, lang: string): string {
|
|
67
|
-
if (key
|
|
68
|
-
key = key
|
|
69
|
-
|
|
67
|
+
if (isLocaleKey(key))
|
|
68
|
+
key = stripLocalePrefix(key)
|
|
69
|
+
|
|
70
70
|
const data = NODE_I18N.locales[lang] || {}
|
|
71
71
|
const keys = key.split('.')
|
|
72
72
|
let result: any = data
|