@ox-content/vite-plugin 0.13.0 → 0.14.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/dist/index.cjs +250 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +249 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -10964,6 +10964,251 @@ function createOgViewerPlugin(options) {
|
|
|
10964
10964
|
};
|
|
10965
10965
|
}
|
|
10966
10966
|
|
|
10967
|
+
//#endregion
|
|
10968
|
+
//#region src/i18n.ts
|
|
10969
|
+
/**
|
|
10970
|
+
* i18n plugin for Ox Content.
|
|
10971
|
+
*
|
|
10972
|
+
* Provides:
|
|
10973
|
+
* - Dictionary loading and validation at build time
|
|
10974
|
+
* - Virtual module for i18n config
|
|
10975
|
+
* - Build-time i18n checking
|
|
10976
|
+
* - Locale-aware routing middleware for dev server
|
|
10977
|
+
*/
|
|
10978
|
+
/**
|
|
10979
|
+
* Resolves i18n options with defaults.
|
|
10980
|
+
*/
|
|
10981
|
+
function resolveI18nOptions(options) {
|
|
10982
|
+
if (options === false) return false;
|
|
10983
|
+
if (!options || !options.enabled) return false;
|
|
10984
|
+
const defaultLocale = options.defaultLocale ?? "en";
|
|
10985
|
+
const locales = options.locales ?? [{
|
|
10986
|
+
code: defaultLocale,
|
|
10987
|
+
name: defaultLocale
|
|
10988
|
+
}];
|
|
10989
|
+
if (!locales.some((l) => l.code === defaultLocale)) locales.unshift({
|
|
10990
|
+
code: defaultLocale,
|
|
10991
|
+
name: defaultLocale
|
|
10992
|
+
});
|
|
10993
|
+
return {
|
|
10994
|
+
enabled: true,
|
|
10995
|
+
dir: options.dir ?? "content/i18n",
|
|
10996
|
+
defaultLocale,
|
|
10997
|
+
locales,
|
|
10998
|
+
hideDefaultLocale: options.hideDefaultLocale ?? true,
|
|
10999
|
+
check: options.check ?? true,
|
|
11000
|
+
functionNames: options.functionNames ?? ["t", "$t"]
|
|
11001
|
+
};
|
|
11002
|
+
}
|
|
11003
|
+
/**
|
|
11004
|
+
* Creates the i18n sub-plugin for the Vite plugin array.
|
|
11005
|
+
*/
|
|
11006
|
+
function createI18nPlugin(resolvedOptions) {
|
|
11007
|
+
const i18nOptions = resolvedOptions.i18n;
|
|
11008
|
+
let root = process.cwd();
|
|
11009
|
+
return {
|
|
11010
|
+
name: "ox-content:i18n",
|
|
11011
|
+
configResolved(config) {
|
|
11012
|
+
root = config.root;
|
|
11013
|
+
},
|
|
11014
|
+
resolveId(id) {
|
|
11015
|
+
if (id === "virtual:ox-content/i18n") return "\0virtual:ox-content/i18n";
|
|
11016
|
+
return null;
|
|
11017
|
+
},
|
|
11018
|
+
load(id) {
|
|
11019
|
+
if (id === "\0virtual:ox-content/i18n") {
|
|
11020
|
+
if (!i18nOptions) return `export const i18n = { enabled: false }; export default i18n;`;
|
|
11021
|
+
return generateI18nModule(i18nOptions, root);
|
|
11022
|
+
}
|
|
11023
|
+
return null;
|
|
11024
|
+
},
|
|
11025
|
+
async buildStart() {
|
|
11026
|
+
if (!i18nOptions || !i18nOptions.check) return;
|
|
11027
|
+
const dictDir = path$1.resolve(root, i18nOptions.dir);
|
|
11028
|
+
if (!fs.existsSync(dictDir)) {
|
|
11029
|
+
console.warn(`[ox-content:i18n] Dictionary directory not found: ${dictDir}`);
|
|
11030
|
+
return;
|
|
11031
|
+
}
|
|
11032
|
+
try {
|
|
11033
|
+
const { loadDictionaries, checkI18n, extractTranslationKeys } = await import("@ox-content/napi");
|
|
11034
|
+
const loadResult = loadDictionaries(dictDir);
|
|
11035
|
+
if (loadResult.errors.length > 0) {
|
|
11036
|
+
for (const error of loadResult.errors) console.warn(`[ox-content:i18n] ${error}`);
|
|
11037
|
+
return;
|
|
11038
|
+
}
|
|
11039
|
+
console.log(`[ox-content:i18n] Loaded ${loadResult.localeCount} locales: ${loadResult.locales.join(", ")}`);
|
|
11040
|
+
const checkResult = checkI18n(dictDir, collectKeysFromSource(root, extractTranslationKeys, i18nOptions));
|
|
11041
|
+
if (checkResult.errorCount > 0 || checkResult.warningCount > 0) {
|
|
11042
|
+
for (const diag of checkResult.diagnostics) if (diag.severity === "error") console.error(`[ox-content:i18n] ${diag.message}`);
|
|
11043
|
+
else if (diag.severity === "warning") console.warn(`[ox-content:i18n] ${diag.message}`);
|
|
11044
|
+
}
|
|
11045
|
+
} catch {}
|
|
11046
|
+
},
|
|
11047
|
+
configureServer(server) {
|
|
11048
|
+
if (!i18nOptions) return;
|
|
11049
|
+
const dictDir = path$1.resolve(root, i18nOptions.dir);
|
|
11050
|
+
if (fs.existsSync(dictDir)) {
|
|
11051
|
+
server.watcher.add(dictDir);
|
|
11052
|
+
server.watcher.on("change", (filePath) => {
|
|
11053
|
+
if (!filePath.startsWith(dictDir)) return;
|
|
11054
|
+
if (!/\.(json|yaml|yml)$/.test(filePath)) return;
|
|
11055
|
+
const mod = server.moduleGraph.getModuleById("\0virtual:ox-content/i18n");
|
|
11056
|
+
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
11057
|
+
server.ws.send({ type: "full-reload" });
|
|
11058
|
+
});
|
|
11059
|
+
}
|
|
11060
|
+
server.middlewares.use((req, _res, next) => {
|
|
11061
|
+
if (!req.url) return next();
|
|
11062
|
+
const localeMatch = req.url.match(/^\/([a-z]{2}(?:-[a-zA-Z]+)?)(\/|$)/);
|
|
11063
|
+
if (localeMatch) {
|
|
11064
|
+
const localeCode = localeMatch[1];
|
|
11065
|
+
if (i18nOptions.locales.some((l) => l.code === localeCode)) req.__oxLocale = localeCode;
|
|
11066
|
+
} else if (i18nOptions.hideDefaultLocale) req.__oxLocale = i18nOptions.defaultLocale;
|
|
11067
|
+
next();
|
|
11068
|
+
});
|
|
11069
|
+
}
|
|
11070
|
+
};
|
|
11071
|
+
}
|
|
11072
|
+
/**
|
|
11073
|
+
* Generates the virtual module for i18n configuration.
|
|
11074
|
+
*/
|
|
11075
|
+
function generateI18nModule(options, root) {
|
|
11076
|
+
const dictDir = path$1.resolve(root, options.dir);
|
|
11077
|
+
const localesJson = JSON.stringify(options.locales);
|
|
11078
|
+
const defaultLocale = JSON.stringify(options.defaultLocale);
|
|
11079
|
+
let dictionariesCode = "{}";
|
|
11080
|
+
try {
|
|
11081
|
+
const napi = require("@ox-content/napi");
|
|
11082
|
+
if (napi.loadDictionariesFlat) {
|
|
11083
|
+
const dictData = napi.loadDictionariesFlat(dictDir);
|
|
11084
|
+
dictionariesCode = JSON.stringify(dictData);
|
|
11085
|
+
} else dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
11086
|
+
} catch {
|
|
11087
|
+
try {
|
|
11088
|
+
dictionariesCode = JSON.stringify(loadDictionariesFallback(options, dictDir));
|
|
11089
|
+
} catch {}
|
|
11090
|
+
}
|
|
11091
|
+
return `
|
|
11092
|
+
export const i18nConfig = {
|
|
11093
|
+
enabled: true,
|
|
11094
|
+
defaultLocale: ${defaultLocale},
|
|
11095
|
+
locales: ${localesJson},
|
|
11096
|
+
hideDefaultLocale: ${JSON.stringify(options.hideDefaultLocale)},
|
|
11097
|
+
};
|
|
11098
|
+
|
|
11099
|
+
export const dictionaries = ${dictionariesCode};
|
|
11100
|
+
|
|
11101
|
+
export function t(key, params, locale) {
|
|
11102
|
+
const dict = dictionaries[locale || i18nConfig.defaultLocale] || {};
|
|
11103
|
+
let message = dict[key];
|
|
11104
|
+
if (!message) {
|
|
11105
|
+
const fallback = dictionaries[i18nConfig.defaultLocale] || {};
|
|
11106
|
+
message = fallback[key] || key;
|
|
11107
|
+
}
|
|
11108
|
+
if (params) {
|
|
11109
|
+
for (const [k, v] of Object.entries(params)) {
|
|
11110
|
+
message = message.replace(new RegExp('\\\\{\\\\$' + k + '\\\\}', 'g'), String(v));
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
11113
|
+
return message;
|
|
11114
|
+
}
|
|
11115
|
+
|
|
11116
|
+
export function getLocaleFromPath(pathname) {
|
|
11117
|
+
const match = pathname.match(/^\\/([a-z]{2}(?:-[a-zA-Z]+)?)(\\//|$)/);
|
|
11118
|
+
if (match) {
|
|
11119
|
+
const code = match[1];
|
|
11120
|
+
if (i18nConfig.locales.some(l => l.code === code)) {
|
|
11121
|
+
return code;
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
return i18nConfig.defaultLocale;
|
|
11125
|
+
}
|
|
11126
|
+
|
|
11127
|
+
export function localePath(pathname, locale) {
|
|
11128
|
+
const current = getLocaleFromPath(pathname);
|
|
11129
|
+
let clean = pathname;
|
|
11130
|
+
if (current !== i18nConfig.defaultLocale || !i18nConfig.hideDefaultLocale) {
|
|
11131
|
+
clean = pathname.replace(new RegExp('^/' + current + '(/|$)'), '/');
|
|
11132
|
+
}
|
|
11133
|
+
if (locale === i18nConfig.defaultLocale && i18nConfig.hideDefaultLocale) {
|
|
11134
|
+
return clean || '/';
|
|
11135
|
+
}
|
|
11136
|
+
return '/' + locale + (clean.startsWith('/') ? clean : '/' + clean);
|
|
11137
|
+
}
|
|
11138
|
+
|
|
11139
|
+
export default { i18nConfig, dictionaries, t, getLocaleFromPath, localePath };
|
|
11140
|
+
`;
|
|
11141
|
+
}
|
|
11142
|
+
/**
|
|
11143
|
+
* Flattens a nested object into dot-separated keys.
|
|
11144
|
+
*/
|
|
11145
|
+
function flattenObject(obj, prefix, result) {
|
|
11146
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
11147
|
+
const fullKey = `${prefix}.${key}`;
|
|
11148
|
+
if (typeof value === "string") result[fullKey] = value;
|
|
11149
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenObject(value, fullKey, result);
|
|
11150
|
+
else result[fullKey] = String(value);
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
11153
|
+
/**
|
|
11154
|
+
* Fallback dictionary loading using TS-based JSON file reading.
|
|
11155
|
+
*/
|
|
11156
|
+
function loadDictionariesFallback(options, dictDir) {
|
|
11157
|
+
const dictData = {};
|
|
11158
|
+
for (const locale of options.locales) {
|
|
11159
|
+
const localeDir = path$1.join(dictDir, locale.code);
|
|
11160
|
+
if (!fs.existsSync(localeDir)) continue;
|
|
11161
|
+
const files = fs.readdirSync(localeDir);
|
|
11162
|
+
const localeDict = {};
|
|
11163
|
+
for (const file of files) {
|
|
11164
|
+
if (!file.endsWith(".json")) continue;
|
|
11165
|
+
const filePath = path$1.join(localeDir, file);
|
|
11166
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
11167
|
+
const namespace = path$1.basename(file, ".json");
|
|
11168
|
+
try {
|
|
11169
|
+
flattenObject(JSON.parse(content), namespace, localeDict);
|
|
11170
|
+
} catch {}
|
|
11171
|
+
}
|
|
11172
|
+
dictData[locale.code] = localeDict;
|
|
11173
|
+
}
|
|
11174
|
+
return dictData;
|
|
11175
|
+
}
|
|
11176
|
+
/**
|
|
11177
|
+
* Collects translation keys from source files using NAPI extractTranslationKeys.
|
|
11178
|
+
*/
|
|
11179
|
+
function collectKeysFromSource(root, extractTranslationKeys, options) {
|
|
11180
|
+
const srcDir = path$1.resolve(root, "src");
|
|
11181
|
+
const keys = /* @__PURE__ */ new Set();
|
|
11182
|
+
if (fs.existsSync(srcDir)) walkDir(srcDir, /\.(ts|tsx|js|jsx)$/, (filePath) => {
|
|
11183
|
+
const usages = extractTranslationKeys(fs.readFileSync(filePath, "utf-8"), filePath, options.functionNames);
|
|
11184
|
+
for (const usage of usages) keys.add(usage.key);
|
|
11185
|
+
});
|
|
11186
|
+
const contentDir = path$1.resolve(root, "content");
|
|
11187
|
+
if (fs.existsSync(contentDir)) {
|
|
11188
|
+
const tPattern = /\{\{t\(['"]([^'"]+)['"]\)\}\}/g;
|
|
11189
|
+
walkDir(contentDir, /\.(md|mdx)$/, (filePath) => {
|
|
11190
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
11191
|
+
let match;
|
|
11192
|
+
while ((match = tPattern.exec(content)) !== null) keys.add(match[1]);
|
|
11193
|
+
tPattern.lastIndex = 0;
|
|
11194
|
+
});
|
|
11195
|
+
}
|
|
11196
|
+
return Array.from(keys);
|
|
11197
|
+
}
|
|
11198
|
+
/**
|
|
11199
|
+
* Recursively walks a directory, calling the callback for files matching the pattern.
|
|
11200
|
+
*/
|
|
11201
|
+
function walkDir(dir, pattern, callback) {
|
|
11202
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
11203
|
+
for (const entry of entries) {
|
|
11204
|
+
const fullPath = path$1.join(dir, entry.name);
|
|
11205
|
+
if (entry.isDirectory()) {
|
|
11206
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
11207
|
+
walkDir(fullPath, pattern, callback);
|
|
11208
|
+
} else if (pattern.test(entry.name)) callback(fullPath);
|
|
11209
|
+
}
|
|
11210
|
+
}
|
|
11211
|
+
|
|
10967
11212
|
//#endregion
|
|
10968
11213
|
//#region src/jsx-runtime.ts
|
|
10969
11214
|
/**
|
|
@@ -11690,6 +11935,7 @@ function oxContent(options = {}) {
|
|
|
11690
11935
|
}
|
|
11691
11936
|
}
|
|
11692
11937
|
];
|
|
11938
|
+
if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
|
|
11693
11939
|
if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
|
|
11694
11940
|
return plugins;
|
|
11695
11941
|
}
|
|
@@ -11719,7 +11965,8 @@ function resolveOptions(options) {
|
|
|
11719
11965
|
transformers: options.transformers ?? [],
|
|
11720
11966
|
docs: resolveDocsOptions(options.docs),
|
|
11721
11967
|
search: resolveSearchOptions(options.search),
|
|
11722
|
-
ogViewer: options.ogViewer ?? true
|
|
11968
|
+
ogViewer: options.ogViewer ?? true,
|
|
11969
|
+
i18n: resolveI18nOptions(options.i18n)
|
|
11723
11970
|
};
|
|
11724
11971
|
}
|
|
11725
11972
|
/**
|
|
@@ -11749,6 +11996,7 @@ exports.buildSsg = buildSsg;
|
|
|
11749
11996
|
exports.clearRenderContext = clearRenderContext;
|
|
11750
11997
|
exports.collectGitHubRepos = require_github.collectGitHubRepos;
|
|
11751
11998
|
exports.collectOgpUrls = require_ogp.collectOgpUrls;
|
|
11999
|
+
exports.createI18nPlugin = createI18nPlugin;
|
|
11752
12000
|
exports.createMarkdownEnvironment = createMarkdownEnvironment;
|
|
11753
12001
|
exports.createTheme = createTheme;
|
|
11754
12002
|
exports.defaultTheme = defaultTheme;
|
|
@@ -11779,6 +12027,7 @@ exports.renderAllPages = renderAllPages;
|
|
|
11779
12027
|
exports.renderPage = renderPage;
|
|
11780
12028
|
exports.renderToString = renderToString;
|
|
11781
12029
|
exports.resolveDocsOptions = resolveDocsOptions;
|
|
12030
|
+
exports.resolveI18nOptions = resolveI18nOptions;
|
|
11782
12031
|
exports.resolveOgImageOptions = resolveOgImageOptions;
|
|
11783
12032
|
exports.resolveSearchOptions = resolveSearchOptions;
|
|
11784
12033
|
exports.resolveSsgOptions = resolveSsgOptions;
|