valaxy 0.28.0-beta.6 → 0.28.0-beta.7
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/composables/locale.ts +68 -9
- package/client/composables/post/index.ts +6 -3
- package/dist/node/cli/index.mjs +1 -1
- package/dist/node/index.d.mts +24 -0
- package/dist/node/index.mjs +1 -1
- package/dist/shared/{valaxy.BVsZMcdc.mjs → valaxy.DAkHYbg0.mjs} +192 -5
- package/package.json +8 -8
- package/shared/node/i18n.ts +4 -4
- package/shared/utils/i18n.ts +55 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Ref } from 'vue'
|
|
2
|
+
import type { TaxonomyNamespace } from '../../shared/utils/i18n'
|
|
2
3
|
import { isClient, useStorage } from '@vueuse/core'
|
|
3
4
|
import dayjs from 'dayjs'
|
|
4
|
-
import { computed } from 'vue'
|
|
5
5
|
|
|
6
|
+
import { computed, watch } from 'vue'
|
|
6
7
|
// not optimize deps all locales
|
|
7
8
|
import { useI18n } from 'vue-i18n'
|
|
8
|
-
import { tObject } from '../../shared/utils/i18n'
|
|
9
|
-
import { LOCALE_PREFIX } from '../utils'
|
|
9
|
+
import { isLocaleKey, resolveTaxonomyLocaleKey, stripLocalePrefix, tObject } from '../../shared/utils/i18n'
|
|
10
10
|
import 'dayjs/locale/en'
|
|
11
11
|
import 'dayjs/locale/zh-cn'
|
|
12
12
|
|
|
@@ -63,13 +63,16 @@ export function useLocale() {
|
|
|
63
63
|
export function useLocaleTitle(fm: Ref<{
|
|
64
64
|
title?: string | Record<string, string>
|
|
65
65
|
} | null>) {
|
|
66
|
-
const { locale } = useI18n()
|
|
66
|
+
const { t, locale } = useI18n()
|
|
67
67
|
return computed(() => {
|
|
68
68
|
if (!fm.value)
|
|
69
69
|
return ''
|
|
70
70
|
|
|
71
71
|
const lang = locale.value
|
|
72
|
-
|
|
72
|
+
const title = tObject(fm.value.title || '', lang) || ''
|
|
73
|
+
if (typeof title === 'string' && isLocaleKey(title))
|
|
74
|
+
return t(stripLocalePrefix(title))
|
|
75
|
+
return title
|
|
73
76
|
})
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -79,16 +82,19 @@ export function useLocaleTitle(fm: Ref<{
|
|
|
79
82
|
* 会从 locales/ 目录中获取对应的翻译
|
|
80
83
|
*/
|
|
81
84
|
export function useValaxyI18n() {
|
|
82
|
-
const { t, locale } = useI18n()
|
|
85
|
+
const { t, te, locale } = useI18n()
|
|
86
|
+
const termCache = new Map<string, string>()
|
|
87
|
+
|
|
88
|
+
// Clear cache on locale switches so each composable instance stays bounded.
|
|
89
|
+
watch(locale, () => termCache.clear())
|
|
83
90
|
|
|
84
91
|
/**
|
|
85
92
|
* translate `$locale:key`
|
|
86
93
|
* @param key
|
|
87
94
|
*/
|
|
88
95
|
const $t = (key: string) => {
|
|
89
|
-
if (key
|
|
90
|
-
return t(key
|
|
91
|
-
}
|
|
96
|
+
if (isLocaleKey(key))
|
|
97
|
+
return t(stripLocalePrefix(key))
|
|
92
98
|
return key
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -104,6 +110,47 @@ export function useValaxyI18n() {
|
|
|
104
110
|
return tObject(data || '', locale.value)
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @en
|
|
115
|
+
* Translate a taxonomy term.
|
|
116
|
+
*
|
|
117
|
+
* Resolution order:
|
|
118
|
+
* 1. `$locale:` prefix → strip and translate via `t()`
|
|
119
|
+
* 2. Locale key `{namespace}.{key}` exists → translate via `t()`
|
|
120
|
+
* 3. Fallback → return the original key as-is
|
|
121
|
+
*
|
|
122
|
+
* The result is cached by `locale + namespace + key` to avoid repeated
|
|
123
|
+
* `te()` / `t()` lookups in tag clouds and category trees.
|
|
124
|
+
*
|
|
125
|
+
* @zh
|
|
126
|
+
* 翻译 taxonomy 术语。
|
|
127
|
+
*
|
|
128
|
+
* 解析顺序:
|
|
129
|
+
* 1. `$locale:` 前缀 → 去掉前缀后通过 `t()` 翻译
|
|
130
|
+
* 2. locale 中存在 `{namespace}.{key}` → 通过 `t()` 翻译
|
|
131
|
+
* 3. 兜底 → 原样返回
|
|
132
|
+
*
|
|
133
|
+
* 结果会按 `locale + namespace + key` 做轻量缓存,
|
|
134
|
+
* 避免标签云和分类树中重复执行 `te()` / `t()`。
|
|
135
|
+
*/
|
|
136
|
+
const $tTerm = (namespace: TaxonomyNamespace, key: string) => {
|
|
137
|
+
const cacheKey = `${locale.value}:${namespace}:${key}`
|
|
138
|
+
const cached = termCache.get(cacheKey)
|
|
139
|
+
if (cached !== undefined)
|
|
140
|
+
return cached
|
|
141
|
+
|
|
142
|
+
const { localeKey, isExplicitLocaleKey } = resolveTaxonomyLocaleKey(namespace, key)
|
|
143
|
+
const result = isExplicitLocaleKey || te(localeKey)
|
|
144
|
+
? `${t(localeKey)}`
|
|
145
|
+
: key
|
|
146
|
+
|
|
147
|
+
termCache.set(cacheKey, result)
|
|
148
|
+
return result
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const $tTag = (key: string) => $tTerm('tag', key)
|
|
152
|
+
const $tCategory = (key: string) => $tTerm('category', key)
|
|
153
|
+
|
|
107
154
|
return {
|
|
108
155
|
locale,
|
|
109
156
|
/**
|
|
@@ -111,5 +158,17 @@ export function useValaxyI18n() {
|
|
|
111
158
|
*/
|
|
112
159
|
$t,
|
|
113
160
|
$tO,
|
|
161
|
+
/**
|
|
162
|
+
* translate taxonomy term (auto-lookup `{namespace}.{key}` in locale files)
|
|
163
|
+
*/
|
|
164
|
+
$tTerm,
|
|
165
|
+
/**
|
|
166
|
+
* translate tag name (auto-lookup `tag.{key}` in locale files)
|
|
167
|
+
*/
|
|
168
|
+
$tTag,
|
|
169
|
+
/**
|
|
170
|
+
* translate category name (auto-lookup `category.{key}` in locale files)
|
|
171
|
+
*/
|
|
172
|
+
$tCategory,
|
|
114
173
|
}
|
|
115
174
|
}
|
|
@@ -6,15 +6,18 @@ import { orderByMeta, useSiteConfig } from 'valaxy'
|
|
|
6
6
|
import { computed } from 'vue'
|
|
7
7
|
import { useI18n } from 'vue-i18n'
|
|
8
8
|
import { useRouterStore } from '../../stores'
|
|
9
|
-
import { tObject } from '../../utils'
|
|
9
|
+
import { isLocaleKey, stripLocalePrefix, tObject } from '../../utils'
|
|
10
10
|
|
|
11
11
|
export * from './usePagination'
|
|
12
12
|
export * from './usePrevNext'
|
|
13
13
|
|
|
14
14
|
export function usePostTitle(post: ComputedRef<Post>) {
|
|
15
|
-
const { locale } = useI18n()
|
|
15
|
+
const { t, locale } = useI18n()
|
|
16
16
|
return computed(() => {
|
|
17
|
-
|
|
17
|
+
const title = tObject(post.value.title || '', locale.value)
|
|
18
|
+
if (typeof title === 'string' && isLocaleKey(title))
|
|
19
|
+
return t(stripLocalePrefix(title))
|
|
20
|
+
return title
|
|
18
21
|
})
|
|
19
22
|
}
|
|
20
23
|
|
package/dist/node/cli/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'node:process';
|
|
2
2
|
import 'yargs';
|
|
3
3
|
import 'yargs/helpers';
|
|
4
|
-
export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.
|
|
4
|
+
export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.DAkHYbg0.mjs';
|
|
5
5
|
import 'node:os';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'consola';
|
package/dist/node/index.d.mts
CHANGED
|
@@ -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
|
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.DAkHYbg0.mjs';
|
|
2
2
|
import 'node:path';
|
|
3
3
|
import 'fs-extra';
|
|
4
4
|
import 'consola/utils';
|
|
@@ -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: {},
|
|
@@ -985,6 +988,30 @@ function tObject(data, lang) {
|
|
|
985
988
|
}
|
|
986
989
|
return data;
|
|
987
990
|
}
|
|
991
|
+
function isLocaleKey(value) {
|
|
992
|
+
return value.startsWith(LOCALE_PREFIX);
|
|
993
|
+
}
|
|
994
|
+
function stripLocalePrefix(value) {
|
|
995
|
+
return isLocaleKey(value) ? value.slice(LOCALE_PREFIX.length) : value;
|
|
996
|
+
}
|
|
997
|
+
function getLocaleMessageValue(messages, key) {
|
|
998
|
+
return key.split(".").reduce((result, part) => result?.[part], messages);
|
|
999
|
+
}
|
|
1000
|
+
function hasLocaleMessage(messages, key) {
|
|
1001
|
+
return getLocaleMessageValue(messages, key) !== void 0;
|
|
1002
|
+
}
|
|
1003
|
+
function resolveTaxonomyLocaleKey(namespace, rawValue) {
|
|
1004
|
+
if (isLocaleKey(rawValue)) {
|
|
1005
|
+
return {
|
|
1006
|
+
localeKey: stripLocalePrefix(rawValue),
|
|
1007
|
+
isExplicitLocaleKey: true
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
localeKey: `${namespace}.${rawValue}`,
|
|
1012
|
+
isExplicitLocaleKey: false
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
988
1015
|
|
|
989
1016
|
const indexRE = /(^|.*\/)index.md(.*)$/i;
|
|
990
1017
|
function linkPlugin(md, externalAttrs, base) {
|
|
@@ -1692,7 +1719,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
|
|
|
1692
1719
|
return md;
|
|
1693
1720
|
}
|
|
1694
1721
|
|
|
1695
|
-
const version = "0.28.0-beta.
|
|
1722
|
+
const version = "0.28.0-beta.7";
|
|
1696
1723
|
|
|
1697
1724
|
const GLOBAL_STATE = {
|
|
1698
1725
|
valaxyApp: void 0,
|
|
@@ -2482,9 +2509,8 @@ function loadLocalesYml(localesPath, force = false) {
|
|
|
2482
2509
|
return locales;
|
|
2483
2510
|
}
|
|
2484
2511
|
function nodeT(key, lang) {
|
|
2485
|
-
if (key
|
|
2486
|
-
key = key
|
|
2487
|
-
}
|
|
2512
|
+
if (isLocaleKey(key))
|
|
2513
|
+
key = stripLocalePrefix(key);
|
|
2488
2514
|
const data = NODE_I18N.locales[lang] || {};
|
|
2489
2515
|
const keys = key.split(".");
|
|
2490
2516
|
let result = data;
|
|
@@ -2529,7 +2555,7 @@ function getSiteUrl(options) {
|
|
|
2529
2555
|
}
|
|
2530
2556
|
|
|
2531
2557
|
function resolveText(value, lang) {
|
|
2532
|
-
if (typeof value === "string" && value
|
|
2558
|
+
if (typeof value === "string" && isLocaleKey(value))
|
|
2533
2559
|
return nodeT(value, lang);
|
|
2534
2560
|
return tObject(value, lang);
|
|
2535
2561
|
}
|
|
@@ -5650,6 +5676,162 @@ const content = {
|
|
|
5650
5676
|
loadAllContent: loadAllContent
|
|
5651
5677
|
};
|
|
5652
5678
|
|
|
5679
|
+
function getConfiguredLanguages(options) {
|
|
5680
|
+
const configured = options.config.siteConfig.languages?.length ? options.config.siteConfig.languages : [options.config.siteConfig.lang || "en"];
|
|
5681
|
+
return [...new Set(configured.filter(Boolean))];
|
|
5682
|
+
}
|
|
5683
|
+
function normalizeToStringArray(value) {
|
|
5684
|
+
if (typeof value === "string")
|
|
5685
|
+
return value.trim() ? [value] : [];
|
|
5686
|
+
if (Array.isArray(value)) {
|
|
5687
|
+
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
5688
|
+
}
|
|
5689
|
+
return [];
|
|
5690
|
+
}
|
|
5691
|
+
async function collectPageFiles(options) {
|
|
5692
|
+
const userPages = await scanPageFiles(options.userRoot, ["**/*.md"]);
|
|
5693
|
+
const userPageFiles = userPages.map((file) => resolve(options.userRoot, "pages", file));
|
|
5694
|
+
const contentDir = resolve(options.tempDir, "content", "pages");
|
|
5695
|
+
if (!await fs.pathExists(contentDir))
|
|
5696
|
+
return userPageFiles;
|
|
5697
|
+
const contentPages = await fg(["**/*.md"], {
|
|
5698
|
+
cwd: contentDir,
|
|
5699
|
+
ignore: ["**/node_modules"]
|
|
5700
|
+
});
|
|
5701
|
+
return [.../* @__PURE__ */ new Set([
|
|
5702
|
+
...userPageFiles,
|
|
5703
|
+
...contentPages.map((file) => resolve(contentDir, file))
|
|
5704
|
+
])];
|
|
5705
|
+
}
|
|
5706
|
+
async function loadMergedLocaleMessages(options) {
|
|
5707
|
+
const languages = getConfiguredLanguages(options);
|
|
5708
|
+
const messages = Object.fromEntries(languages.map((lang) => [lang, {}]));
|
|
5709
|
+
for (const root of options.roots) {
|
|
5710
|
+
for (const lang of languages) {
|
|
5711
|
+
const localeFiles = [
|
|
5712
|
+
resolve(root, "locales", `${lang}.yml`),
|
|
5713
|
+
resolve(root, "locales", `${lang}.yaml`)
|
|
5714
|
+
];
|
|
5715
|
+
for (const localeFile of localeFiles) {
|
|
5716
|
+
if (!await fs.pathExists(localeFile))
|
|
5717
|
+
continue;
|
|
5718
|
+
const content = await fs.readFile(localeFile, "utf-8");
|
|
5719
|
+
if (!content.trim())
|
|
5720
|
+
continue;
|
|
5721
|
+
const data = yaml.load(content);
|
|
5722
|
+
messages[lang] = replaceArrMerge(data || {}, messages[lang]);
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
return messages;
|
|
5727
|
+
}
|
|
5728
|
+
async function collectTaxonomyUsages(options) {
|
|
5729
|
+
const files = await collectPageFiles(options);
|
|
5730
|
+
const posts = await readPostFiles(files);
|
|
5731
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
5732
|
+
const addUsage = (namespace, rawValue, file) => {
|
|
5733
|
+
const key = `${namespace}:${rawValue}`;
|
|
5734
|
+
if (!usageMap.has(key)) {
|
|
5735
|
+
usageMap.set(key, {
|
|
5736
|
+
namespace,
|
|
5737
|
+
rawValue,
|
|
5738
|
+
files: /* @__PURE__ */ new Set()
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
usageMap.get(key).files.add(file);
|
|
5742
|
+
};
|
|
5743
|
+
for (const post of posts) {
|
|
5744
|
+
const file = relative(options.userRoot, post.filePath);
|
|
5745
|
+
for (const tag of normalizeToStringArray(post.data.tags))
|
|
5746
|
+
addUsage("tag", tag, file);
|
|
5747
|
+
for (const category of normalizeToStringArray(post.data.categories))
|
|
5748
|
+
addUsage("category", category, file);
|
|
5749
|
+
}
|
|
5750
|
+
return Array.from(usageMap.values(), (item) => ({
|
|
5751
|
+
namespace: item.namespace,
|
|
5752
|
+
rawValue: item.rawValue,
|
|
5753
|
+
files: [...item.files].sort()
|
|
5754
|
+
})).sort((a, b) => `${a.namespace}:${a.rawValue}`.localeCompare(`${b.namespace}:${b.rawValue}`));
|
|
5755
|
+
}
|
|
5756
|
+
function findTaxonomyI18nIssues(usages, messages, languages) {
|
|
5757
|
+
const issues = [];
|
|
5758
|
+
for (const usage of usages) {
|
|
5759
|
+
const { namespace, rawValue, files } = usage;
|
|
5760
|
+
const { localeKey, isExplicitLocaleKey } = resolveTaxonomyLocaleKey(namespace, rawValue);
|
|
5761
|
+
const presentLanguages = languages.filter((lang) => hasLocaleMessage(messages[lang], localeKey));
|
|
5762
|
+
const missingLanguages = languages.filter((lang) => !hasLocaleMessage(messages[lang], localeKey));
|
|
5763
|
+
if (isExplicitLocaleKey) {
|
|
5764
|
+
if (missingLanguages.length) {
|
|
5765
|
+
issues.push({
|
|
5766
|
+
type: "explicit-locale-key-missing",
|
|
5767
|
+
namespace,
|
|
5768
|
+
rawValue,
|
|
5769
|
+
localeKey,
|
|
5770
|
+
presentLanguages,
|
|
5771
|
+
missingLanguages,
|
|
5772
|
+
files
|
|
5773
|
+
});
|
|
5774
|
+
}
|
|
5775
|
+
continue;
|
|
5776
|
+
}
|
|
5777
|
+
if (presentLanguages.length === 0 || presentLanguages.length === languages.length)
|
|
5778
|
+
continue;
|
|
5779
|
+
issues.push({
|
|
5780
|
+
type: "partial-locale-coverage",
|
|
5781
|
+
namespace,
|
|
5782
|
+
rawValue,
|
|
5783
|
+
localeKey,
|
|
5784
|
+
presentLanguages,
|
|
5785
|
+
missingLanguages,
|
|
5786
|
+
files
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
return issues;
|
|
5790
|
+
}
|
|
5791
|
+
function formatFiles(files) {
|
|
5792
|
+
const preview = files.slice(0, 3).map((file) => colors.dim(file)).join(", ");
|
|
5793
|
+
if (files.length <= 3)
|
|
5794
|
+
return preview;
|
|
5795
|
+
return `${preview}${colors.dim(` and ${files.length - 3} more`)}`;
|
|
5796
|
+
}
|
|
5797
|
+
function logTaxonomyI18nIssues(issues, level) {
|
|
5798
|
+
if (!issues.length)
|
|
5799
|
+
return;
|
|
5800
|
+
const log = level === "error" ? consola.error : consola.warn;
|
|
5801
|
+
const count = level === "error" ? colors.red(String(issues.length)) : colors.yellow(String(issues.length));
|
|
5802
|
+
log(`Detected ${count} taxonomy i18n issue(s).`);
|
|
5803
|
+
for (const issue of issues) {
|
|
5804
|
+
const where = formatFiles(issue.files);
|
|
5805
|
+
if (issue.type === "explicit-locale-key-missing") {
|
|
5806
|
+
log(
|
|
5807
|
+
`[taxonomy-i18n] ${colors.cyan(issue.rawValue)} is an explicit locale key but ${colors.red(`missing in: ${issue.missingLanguages.join(", ")}`)}. Files: ${where}`
|
|
5808
|
+
);
|
|
5809
|
+
continue;
|
|
5810
|
+
}
|
|
5811
|
+
log(
|
|
5812
|
+
`[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}`
|
|
5813
|
+
);
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
function resolveTaxonomyI18nValidationLevel(options) {
|
|
5817
|
+
return options.config.build.taxonomyI18n?.level || "warn";
|
|
5818
|
+
}
|
|
5819
|
+
async function validateTaxonomyI18n(options) {
|
|
5820
|
+
const level = resolveTaxonomyI18nValidationLevel(options);
|
|
5821
|
+
if (level === "off")
|
|
5822
|
+
return [];
|
|
5823
|
+
const languages = getConfiguredLanguages(options);
|
|
5824
|
+
const [messages, usages] = await Promise.all([
|
|
5825
|
+
loadMergedLocaleMessages(options),
|
|
5826
|
+
collectTaxonomyUsages(options)
|
|
5827
|
+
]);
|
|
5828
|
+
const issues = findTaxonomyI18nIssues(usages, messages, languages);
|
|
5829
|
+
logTaxonomyI18nIssues(issues, level);
|
|
5830
|
+
if (level === "error" && issues.length)
|
|
5831
|
+
throw new Error(`Taxonomy i18n validation failed with ${issues.length} issue(s).`);
|
|
5832
|
+
return issues;
|
|
5833
|
+
}
|
|
5834
|
+
|
|
5653
5835
|
function getServerInfoText(msg) {
|
|
5654
5836
|
return `${valaxyPrefix} ${colors.gray(msg)}`;
|
|
5655
5837
|
}
|
|
@@ -5790,6 +5972,7 @@ async function execBuild({ ssg, ssgEngine, root, output, log }) {
|
|
|
5790
5972
|
);
|
|
5791
5973
|
await callHookWithLog("config:init", valaxyApp);
|
|
5792
5974
|
await callHookWithLog("build:before", valaxyApp);
|
|
5975
|
+
await validateTaxonomyI18n(options);
|
|
5793
5976
|
consola.box("\u{1F320} Start building...");
|
|
5794
5977
|
try {
|
|
5795
5978
|
if (ssg) {
|
|
@@ -6075,6 +6258,7 @@ async function startValaxyDev({
|
|
|
6075
6258
|
await valaxyApp.hooks.callHook("content:before-load");
|
|
6076
6259
|
await loadAllContent(loaders, ctx);
|
|
6077
6260
|
await valaxyApp.hooks.callHook("content:loaded");
|
|
6261
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6078
6262
|
for (const loader of loaders) {
|
|
6079
6263
|
if (loader.devPollInterval) {
|
|
6080
6264
|
const poll = async () => {
|
|
@@ -6082,6 +6266,7 @@ async function startValaxyDev({
|
|
|
6082
6266
|
await valaxyApp.hooks.callHook("content:before-load");
|
|
6083
6267
|
await loadAllContent([loader], ctx);
|
|
6084
6268
|
await valaxyApp.hooks.callHook("content:loaded");
|
|
6269
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6085
6270
|
} catch (error) {
|
|
6086
6271
|
consola.error("[content-loader] Error while polling:", error);
|
|
6087
6272
|
} finally {
|
|
@@ -6092,6 +6277,8 @@ async function startValaxyDev({
|
|
|
6092
6277
|
setTimeout(poll, loader.devPollInterval);
|
|
6093
6278
|
}
|
|
6094
6279
|
}
|
|
6280
|
+
} else {
|
|
6281
|
+
await validateTaxonomyI18n(resolvedOptions);
|
|
6095
6282
|
}
|
|
6096
6283
|
const viteConfig = mergeConfig({
|
|
6097
6284
|
// initial vite config
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "valaxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.28.0-beta.
|
|
4
|
+
"version": "0.28.0-beta.7",
|
|
5
5
|
"description": "📄 Vite & Vue powered static blog generator.",
|
|
6
6
|
"author": {
|
|
7
7
|
"email": "me@yunyoujun.cn",
|
|
@@ -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.40",
|
|
99
99
|
"lru-cache": "^11.2.7",
|
|
100
100
|
"markdown-it": "^14.1.1",
|
|
101
101
|
"markdown-it-anchor": "^9.2.0",
|
|
@@ -110,7 +110,7 @@
|
|
|
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,19 @@
|
|
|
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.
|
|
132
|
+
"vite": "^8.0.1",
|
|
133
133
|
"vite-dev-rpc": "^1.1.0",
|
|
134
134
|
"vite-plugin-vue-devtools": "^8.1.0",
|
|
135
|
-
"vite-plugin-vue-layouts-next": "^2.0
|
|
135
|
+
"vite-plugin-vue-layouts-next": "^2.1.0",
|
|
136
136
|
"vite-ssg": "^28.3.0",
|
|
137
137
|
"vite-ssg-sitemap": "^0.10.0",
|
|
138
138
|
"vitepress-plugin-group-icons": "^1.7.1",
|
|
139
139
|
"vue": "3.5.22",
|
|
140
140
|
"vue-i18n": "^11.3.0",
|
|
141
|
-
"vue-router": "^5.0.
|
|
141
|
+
"vue-router": "^5.0.4",
|
|
142
142
|
"yargs": "^18.0.0",
|
|
143
|
-
"@valaxyjs/
|
|
144
|
-
"@valaxyjs/
|
|
143
|
+
"@valaxyjs/utils": "0.28.0-beta.7",
|
|
144
|
+
"@valaxyjs/devtools": "0.28.0-beta.7"
|
|
145
145
|
},
|
|
146
146
|
"devDependencies": {
|
|
147
147
|
"@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
|
package/shared/utils/i18n.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { LOCALE_PREFIX } from '../constants'
|
|
2
|
+
|
|
3
|
+
export type TaxonomyNamespace = 'tag' | 'category'
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* translate object
|
|
3
7
|
*
|
|
@@ -15,3 +19,54 @@ export function tObject<T = string>(data: string | Record<string, T>, lang: stri
|
|
|
15
19
|
}
|
|
16
20
|
return data
|
|
17
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the value is an explicit locale key like `$locale:tag.notes`.
|
|
25
|
+
*/
|
|
26
|
+
export function isLocaleKey(value: string): boolean {
|
|
27
|
+
return value.startsWith(LOCALE_PREFIX)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Strip `$locale:` prefix when present.
|
|
32
|
+
*/
|
|
33
|
+
export function stripLocalePrefix(value: string): string {
|
|
34
|
+
return isLocaleKey(value) ? value.slice(LOCALE_PREFIX.length) : value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a nested locale message value by dot-separated key.
|
|
39
|
+
*/
|
|
40
|
+
export function getLocaleMessageValue(messages: Record<string, any> | undefined, key: string): any {
|
|
41
|
+
return key.split('.').reduce<any>((result, part) => result?.[part], messages)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether a locale message exists for the given key.
|
|
46
|
+
*/
|
|
47
|
+
export function hasLocaleMessage(messages: Record<string, any> | undefined, key: string): boolean {
|
|
48
|
+
return getLocaleMessageValue(messages, key) !== undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the effective locale key for a taxonomy term.
|
|
53
|
+
*
|
|
54
|
+
* - `$locale:tag.notes` -> `tag.notes` (explicit locale key)
|
|
55
|
+
* - `notes` in `tag` namespace -> `tag.notes`
|
|
56
|
+
*/
|
|
57
|
+
export function resolveTaxonomyLocaleKey(namespace: TaxonomyNamespace, rawValue: string): {
|
|
58
|
+
localeKey: string
|
|
59
|
+
isExplicitLocaleKey: boolean
|
|
60
|
+
} {
|
|
61
|
+
if (isLocaleKey(rawValue)) {
|
|
62
|
+
return {
|
|
63
|
+
localeKey: stripLocalePrefix(rawValue),
|
|
64
|
+
isExplicitLocaleKey: true,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
localeKey: `${namespace}.${rawValue}`,
|
|
70
|
+
isExplicitLocaleKey: false,
|
|
71
|
+
}
|
|
72
|
+
}
|