vite-plugin-vue-i18n-typescript
Version:
Type-safe Vue i18n translations. Auto-generates TypeScript types from JSON locale files. Catch translation errors at compile time with full IDE autocomplete.
1,222 lines (1,170 loc) • 47.1 kB
JavaScript
;
var path6 = require('path');
var vite = require('vite');
var fs$1 = require('fs');
var vueI18n = require('vue-i18n');
var fs = require('fs/promises');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var path6__default = /*#__PURE__*/_interopDefault(path6);
var fs__default = /*#__PURE__*/_interopDefault(fs);
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
__defProp(target, "default", { value: mod, enumerable: true }) ,
mod
));
// node_modules/picocolors/picocolors.js
var require_picocolors = __commonJS({
"node_modules/picocolors/picocolors.js"(exports, module) {
var p = process || {};
var argv = p.argv || [];
var env = p.env || {};
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
var formatter = (open, close, replace = open) => (input) => {
let string = "" + input, index = string.indexOf(close, open.length);
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
};
var replaceClose = (string, close, replace, index) => {
let result = "", cursor = 0;
do {
result += string.substring(cursor, index) + replace;
cursor = index + close.length;
index = string.indexOf(close, cursor);
} while (~index);
return result + string.substring(cursor);
};
var createColors = (enabled = isColorSupported) => {
let f = enabled ? formatter : () => String;
return {
isColorSupported: enabled,
reset: f("\x1B[0m", "\x1B[0m"),
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
italic: f("\x1B[3m", "\x1B[23m"),
underline: f("\x1B[4m", "\x1B[24m"),
inverse: f("\x1B[7m", "\x1B[27m"),
hidden: f("\x1B[8m", "\x1B[28m"),
strikethrough: f("\x1B[9m", "\x1B[29m"),
black: f("\x1B[30m", "\x1B[39m"),
red: f("\x1B[31m", "\x1B[39m"),
green: f("\x1B[32m", "\x1B[39m"),
yellow: f("\x1B[33m", "\x1B[39m"),
blue: f("\x1B[34m", "\x1B[39m"),
magenta: f("\x1B[35m", "\x1B[39m"),
cyan: f("\x1B[36m", "\x1B[39m"),
white: f("\x1B[37m", "\x1B[39m"),
gray: f("\x1B[90m", "\x1B[39m"),
bgBlack: f("\x1B[40m", "\x1B[49m"),
bgRed: f("\x1B[41m", "\x1B[49m"),
bgGreen: f("\x1B[42m", "\x1B[49m"),
bgYellow: f("\x1B[43m", "\x1B[49m"),
bgBlue: f("\x1B[44m", "\x1B[49m"),
bgMagenta: f("\x1B[45m", "\x1B[49m"),
bgCyan: f("\x1B[46m", "\x1B[49m"),
bgWhite: f("\x1B[47m", "\x1B[49m"),
blackBright: f("\x1B[90m", "\x1B[39m"),
redBright: f("\x1B[91m", "\x1B[39m"),
greenBright: f("\x1B[92m", "\x1B[39m"),
yellowBright: f("\x1B[93m", "\x1B[39m"),
blueBright: f("\x1B[94m", "\x1B[39m"),
magentaBright: f("\x1B[95m", "\x1B[39m"),
cyanBright: f("\x1B[96m", "\x1B[39m"),
whiteBright: f("\x1B[97m", "\x1B[39m"),
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
bgRedBright: f("\x1B[101m", "\x1B[49m"),
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
};
};
module.exports = createColors();
module.exports.createColors = createColors;
}
});
function defaultGetLocaleFromPath(filePath) {
const fileName = path6__default.default.basename(filePath);
const parts = fileName.split(".");
if (parts.length < 2 || parts[parts.length - 1] !== "json") {
return null;
}
const regex = /^(?=.*(?:^|\.)(?<locale>[a-z]{2}(?:-[A-Z]{2})?(?=\.|$))).*\.json$/;
const regexValid = fileName.match(regex);
if (!regexValid) {
return null;
}
if (fileName.startsWith(".")) {
return null;
}
const locale = regexValid.groups?.locale;
return locale && locale?.length > 0 ? locale : null;
}
// src/generation/runtime/merge.ts
var shallowMerge = (a, b) => Object.assign({}, a, b);
var toArray = (v) => Array.isArray(v) ? v : v ? [v] : [];
// src/generation/runtime/deepMerge.ts
function deepMerge(target, source) {
if (target === source) return target;
if (Array.isArray(target) && Array.isArray(source)) return source;
if (target && source && typeof target === "object" && typeof source === "object" && !Array.isArray(target) && !Array.isArray(source)) {
const out = { ...target };
for (const key of Object.keys(source)) {
out[key] = key in target ? deepMerge(target[key], source[key]) : source[key];
}
return out;
}
return source;
}
// src/core/config.ts
function normalizeConfig(userOptions = {}, logger, cfg) {
const baseLocale = userOptions.baseLocale ?? "de";
const root = cfg?.root ?? userOptions.root ?? process.cwd();
function resolvePattern(pattern) {
if (!pattern) throw new Error("Invalid pattern: empty string");
const isNegated = pattern.startsWith("!");
const rawPattern = isNegated ? pattern.slice(1) : pattern;
const trimmedPattern = rawPattern.startsWith("./") ? rawPattern.slice(2) : rawPattern;
const absolutePattern = path6__default.default.isAbsolute(rawPattern) ? rawPattern : path6__default.default.join(root, trimmedPattern);
const normalized = vite.normalizePath(absolutePattern);
return isNegated ? `!${normalized}` : normalized;
}
const sourceId = userOptions.sourceId ?? "virtual:vue-i18n-types";
const config = {
...userOptions,
sourceId,
root,
fileBatchSize: userOptions.fileBatchSize ?? 100,
verbose: userOptions.debug ?? false,
typesPath: userOptions.typesPath ?? "./src/vite-env-override.d.ts",
virtualFilePath: userOptions.virtualFilePath,
getLocaleFromPath: userOptions.getLocaleFromPath ?? defaultGetLocaleFromPath,
baseLocale,
include: (Array.isArray(userOptions.include) ? userOptions.include : userOptions.include ? [userOptions.include] : [
"./**/locales/*.json",
"./**/*.vue.*.json",
`./**/*${baseLocale}.json`
]).map(resolvePattern),
exclude: (Array.isArray(userOptions.exclude) ? userOptions.exclude : userOptions.exclude ? [userOptions.exclude] : [
"**/tsconfig.*",
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
"**/.output/**",
"**/.vercel/**",
"**/.next/**",
"**/build/**"
]).map((pattern) => {
const resolved = resolvePattern(pattern);
return resolved.startsWith("!") ? resolved : `!${resolved}`;
}).map((pattern) => {
const resolved = resolvePattern(pattern);
return resolved.startsWith("!") ? resolved : `!${resolved}`;
}),
merge: userOptions.merge ?? "deep",
mergeFunction: (userOptions.merge ?? "deep") === "deep" ? deepMerge : shallowMerge,
banner: userOptions.banner,
debug: userOptions.debug ?? false,
logger,
transformJson: userOptions.transformJson
};
return config;
}
// src/utils/keys.ts
function transformKeys(keys) {
const set = /* @__PURE__ */ new Set();
for (const k of keys) {
if (typeof k === "string" && k) set.add(k);
}
return Array.from(set).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
}
// src/utils/json.ts
function getJsonLeafPaths(obj) {
const paths = [];
const isPlainObject = (val) => Object.prototype.toString.call(val) === "[object Object]";
function walk(value, currentPath) {
if (value == null) {
if (currentPath) paths.push(currentPath);
return;
}
if (Array.isArray(value)) {
if (currentPath) paths.push(currentPath);
for (let i = 0; i < value.length; i++) {
const next = currentPath ? `${currentPath}.${i}` : String(i);
walk(value[i], next);
}
return;
}
if (isPlainObject(value)) {
const entries = Object.entries(value);
if (entries.length === 0) {
return;
}
for (const [k, v] of entries) {
const next = currentPath ? `${currentPath}.${k}` : k;
walk(v, next);
}
return;
}
if (currentPath) {
paths.push(currentPath);
}
}
walk(obj, "");
return paths;
}
function canonicalize(value) {
if (value === null || typeof value !== "object") return value;
if (Array.isArray(value)) {
const arr = value;
const out2 = new Array(arr.length);
for (let i = 0; i < arr.length; i++) out2[i] = canonicalize(arr[i]);
return out2;
}
const obj = value;
const keys = Object.keys(obj).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
const out = {};
for (const k of keys) out[k] = canonicalize(obj[k]);
return out;
}
function detectKeyConflicts(messages3) {
const conflicts = [];
const keyTypeMap = /* @__PURE__ */ new Map();
const getValueType = (val) => {
if (val === null) return "null";
if (val === void 0) return "undefined";
if (Array.isArray(val)) return "array";
if (typeof val === "object") return "object";
return typeof val;
};
const collectKeyTypes = (obj, locale, path7 = "") => {
if (obj === null || obj === void 0) {
if (path7) {
if (!keyTypeMap.has(path7)) keyTypeMap.set(path7, /* @__PURE__ */ new Map());
keyTypeMap.get(path7).set(locale, getValueType(obj));
}
return;
}
if (Array.isArray(obj)) {
if (path7) {
if (!keyTypeMap.has(path7)) keyTypeMap.set(path7, /* @__PURE__ */ new Map());
keyTypeMap.get(path7).set(locale, "array");
}
return;
}
if (typeof obj === "object") {
if (path7) {
if (!keyTypeMap.has(path7)) keyTypeMap.set(path7, /* @__PURE__ */ new Map());
keyTypeMap.get(path7).set(locale, "object");
}
for (const [key, value] of Object.entries(obj)) {
const newPath = path7 ? `${path7}.${key}` : key;
collectKeyTypes(value, locale, newPath);
}
return;
}
if (path7) {
if (!keyTypeMap.has(path7)) keyTypeMap.set(path7, /* @__PURE__ */ new Map());
keyTypeMap.get(path7).set(locale, getValueType(obj));
}
};
for (const [locale, localeMessages] of Object.entries(messages3)) {
if (locale === "js-reserved") continue;
collectKeyTypes(localeMessages, locale);
}
for (const [key, localeTypes] of keyTypeMap.entries()) {
const types = Array.from(localeTypes.entries());
const uniqueTypes = new Set(types.map(([_, type]) => type));
if (uniqueTypes.size > 1) {
const typeGroups = /* @__PURE__ */ new Map();
for (const [locale, type] of types) {
if (!typeGroups.has(type)) typeGroups.set(type, []);
typeGroups.get(type).push(locale);
}
const groupDescriptions = Array.from(typeGroups.entries()).map(([type, locales]) => `${type} in [${locales.join(", ")}]`).join(" vs ");
conflicts.push(`Key "${key}" has conflicting types: ${groupDescriptions}`);
}
}
return conflicts;
}
function getFinalKeys(messages3, baseLocale) {
const messagesForBaseLocale = messages3?.[baseLocale] ?? Object.values(messages3)[0];
const allKeysRaw = getJsonLeafPaths(messagesForBaseLocale).map((p) => p.replace(/\.body\.cases$/g, ""));
return transformKeys(allKeysRaw);
}
// src/utils/error-formatter.ts
function parseJSONWithLocation(content, filePath) {
try {
return JSON.parse(content);
} catch (error) {
const location = extractJSONErrorLocation(error, content);
throw createFormattedError(filePath, content, location, error.message);
}
}
function extractJSONErrorLocation(error, content) {
const message = error.message;
const positionMatch = message.match(/at position (\d+)/);
if (positionMatch) {
const position = parseInt(positionMatch[1], 10);
return getLineAndColumn(content, position);
}
const lineColMatch = message.match(/line (\d+) column (\d+)/);
if (lineColMatch) {
return {
line: parseInt(lineColMatch[1], 10),
column: parseInt(lineColMatch[2], 10)
};
}
return null;
}
function getLineAndColumn(content, position) {
const lines = content.substring(0, position).split("\n");
const line = lines.length;
const column = lines[lines.length - 1].length + 1;
return { line, column };
}
function createFormattedError(filePath, content, location, originalMessage) {
if (!location) {
const error2 = new Error(`Failed to parse JSON in ${filePath}
${originalMessage}`);
error2.name = "JSONParseError";
return error2;
}
const lines = content.split("\n");
const { line, column } = location;
const contextStart = Math.max(0, line - 3);
const contextEnd = Math.min(lines.length, line + 2);
const contextLines = lines.slice(contextStart, contextEnd);
const errorLines = [];
errorLines.push(`
\u274C JSON Parse Error in ${filePath}:${line}:${column}`);
errorLines.push("");
errorLines.push(originalMessage);
errorLines.push("");
contextLines.forEach((contextLine, index) => {
const lineNum = contextStart + index + 1;
const isErrorLine = lineNum === line;
const prefix = isErrorLine ? ">" : " ";
const lineNumStr = String(lineNum).padStart(4, " ");
errorLines.push(`${prefix} ${lineNumStr} | ${contextLine}`);
if (isErrorLine && column > 0) {
const pointer = " ".repeat(8 + column) + "^";
errorLines.push(pointer);
}
});
errorLines.push("");
const error = new Error(errorLines.join("\n"));
error.name = "JSONParseError";
return error;
}
function wrapErrorWithFile(filePath, error) {
if (error.name === "JSONParseError" || error.name === "FileError") {
return error;
}
const wrappedError = new Error(
`
\u274C Error processing ${filePath}
${error.message}
`
);
wrappedError.name = "FileProcessingError";
wrappedError.stack = error.stack;
return wrappedError;
}
// src/generation/runtime/translateWrapperFunction.ts??inline
function translateWrapperFunction(fn) {
return ((...args) => {
if (args.length >= 2) {
const secondArg = args[1];
console.log(args, typeof secondArg);
if (typeof secondArg === "number" || typeof secondArg === "string" && !isNaN(parseFloat(secondArg))) {
const numericValue = parseFloat(secondArg.toString());
const originalThirdArg = args[2];
const newThirdArg = typeof originalThirdArg === "object" && originalThirdArg !== null && !Array.isArray(originalThirdArg) ? { ...originalThirdArg, count: numericValue, n: numericValue } : { count: numericValue, n: numericValue };
const newArgs = [
args[0],
// First argument
// numericValue, // Converted second argument
newThirdArg
// Modified or created third argument
];
console.log(newThirdArg);
return fn.apply(this, newArgs);
}
}
return fn.apply(this, args);
});
}
var translateWrapperFunction_default = translateWrapperFunction;
// src/generation/runtime/deepMerge.ts??inline
function deepMerge2(target, source) {
if (target === source) return target;
if (Array.isArray(target) && Array.isArray(source)) return source;
if (target && source && typeof target === "object" && typeof source === "object" && !Array.isArray(target) && !Array.isArray(source)) {
const out = { ...target };
for (const key of Object.keys(source)) {
out[key] = key in target ? deepMerge2(target[key], source[key]) : source[key];
}
return out;
}
return source;
}
// src/generation/runtime/hrmHotUpdate.ts??inline
function hrmHotUpdate(messages3, data, app, deepMerge3) {
const i18nModule = app ?? globalThis?.i18nModule?.global;
if (!i18nModule) {
console.error("[i18n hotUpdate] No i18n module instance found, skipping update.");
return;
}
if (!data?.messages?.languages?.length && !data?.locale) {
console.warn("[i18n hotUpdate] No languages found in hot update data, skipping update.", data);
return;
}
if (data.messages) {
console.debug(`[i18n hotUpdate] Received FULL hot update with ${data.messages.contentId}: ${data.messages.languages.join(",")} for files: ${data.files.join(", ")}`);
const mergedMessages = data.messages.messages;
if (i18nModule) {
data.messages.languages.forEach((locale) => {
i18nModule.setLocaleMessage(locale, mergedMessages[locale]);
});
}
} else {
console.trace("[i18n hotUpdate] Received hot update for locale: " + data.locale);
i18nModule.mergeLocaleMessage(data.locale, data.update);
}
const currentLocale = toValue(i18nModule.locale);
if (!currentLocale) {
console.warn("[i18n hotUpdate] Current locale is undefined, skipping locale re-assignment.");
return;
}
if (isReactive(i18nModule.locale)) {
i18nModule.locale.value = currentLocale;
} else {
i18nModule.locale = data.locale || currentLocale;
}
}
var hrmHotUpdate_default = hrmHotUpdate;
function createI18nInstance(options) {
const i18nApp = vueI18n.createI18n({
fallbackLocale: fallbackLocales,
// missingWarn: false,
// fallbackWarn: false,
locale: navigator?.language,
legacy: false,
...options,
messages
});
globalThis.i18nApp = i18nApp;
return i18nApp;
}
var createI18nInstance_default = createI18nInstance;
// src/generation/treeShakeGenerator.ts
var imports = "_imports" /* imports */;
var messages2 = "messages" /* messages */;
var hrmHotUpdate2 = "_hrmHotUpdate" /* hrmHotUpdate */;
var availableLocales = "availableLocales" /* availableLocales */;
var useI18nApp = "useI18nApp" /* useI18nApp */;
var fallbackLocales2 = "fallbackLocales" /* fallbackLocales */;
var translationWrapper = "translationWrapper" /* translationWrapper */;
var createI18nInstance2 = "createI18nInstance" /* createI18nInstance */;
var createI18nInstancePlugin = "createI18nInstancePlugin" /* createI18nInstancePlugin */;
var useI18nTypeSafe = "useI18nTypeSafe" /* useI18nTypeSafe */;
var codeHrmHotUpdate = `
let cachedMessages = {};
if (import.meta.hot) {
${deepMerge2}
${hrmHotUpdate_default}
import.meta.hot.on('i18n-update', (data) => {
cachedMessages = hrmHotUpdate(cachedMessages,data, globalThis.i18nApp.global,deepMerge);
});
}`;
function buildRuntimeMethods(messagesCombined) {
messagesCombined.config;
function getMessages() {
return `export const messages = ${messagesCombined.messagesJsonString};`;
}
return {
[imports]: `
import { isReactive, ref, toValue} from "vue";
import { createI18n, useI18n } from 'vue-i18n';
`,
[messages2]: getMessages(),
[availableLocales]: `export const availableLocales = ${messagesCombined.languagesTuple()};`,
[fallbackLocales2]: `export const fallbackLocales = ${JSON.stringify(messagesCombined.fallbackLocales)};`,
[hrmHotUpdate2]: codeHrmHotUpdate,
[useI18nApp]: "export const useI18nApp = () => globalThis.i18nApp.global;",
[translationWrapper]: "export " + translateWrapperFunction_default,
[createI18nInstance2]: "export " + createI18nInstance_default,
[createI18nInstancePlugin]: "export const createI18nInstancePlugin = createI18nInstance;",
[useI18nTypeSafe]: `export function useI18nTypeSafe(options) {
const { t: originalT, d, n, ...rest } = useI18n({
...(options ?? {}),
});
return {
...rest,
t: translateWrapperFunction(originalT),
d,
n,
};
}`
};
}
var RuntimeMethods = class {
_data;
_config;
constructor(messagesCombined) {
this._data = buildRuntimeMethods(messagesCombined);
this._config = messagesCombined.config;
}
/** Deterministic full-file content (all sections in HelperMethodsOrder) */
toFileContent(defaultExport = "messages") {
return Object.values(this._data).join("\n\n") + "export default " + defaultExport;
}
parseSymbolEnumKey(key) {
for (const [keyFound, contentFound] of Object.entries(this._data)) {
if (keyFound.toString() == key.toString()) {
return contentFound;
}
}
return void 0;
}
getFileContentFor(target, separator = "\n\n") {
const resolvedCode = this.parseSymbolEnumKey(target);
if (!resolvedCode) {
this._config.logger.error(`getFileContentFor: No code found for target "${target}". Available keys: ${Object.entries(this._data).join(", ")}`);
return this.toFileContent(target);
}
const importsCode = this._data[imports];
const defaultExportCode = `export default ${target}` ;
const parts = [importsCode, resolvedCode, defaultExportCode].filter(Boolean);
return parts.join(separator);
}
};
// src/generation/generator.ts
function toTypesContent(params) {
const {
combinedMessages,
config
} = params;
const sourceId = config?.sourceId ?? "@vue-i18n-types";
const contentId = combinedMessages.keysId;
const autogenerated = config?.banner ?? //// @ts-nocheck
`/* eslint-disable */
/* prettier-ignore */
// @formatter:off
// biome-ignore lint: disable
// AUTO-GENERATED FILE. DO NOT EDIT.
// Content-Hash: ${contentId}
`.split("\n").map((l) => l.trim()).filter((l) => l).join("\n") + "\n\n";
const NL = "\n";
const baseLocaleMessages = combinedMessages.baseLocaleMessages;
const finalKeys = combinedMessages.keys;
const availableLocales2 = combinedMessages.languages;
const bodyLines = [
"// types content",
`
declare module '${sourceId}/messages' {
import type {ResourceValue,TranslationsPaths,PickupPaths,RemoveIndexSignature,PickupKeys,PickupLocales, ResourcePath, IsEmptyObject,} from "@intlify/core-base";
import type { DefineLocaleMessage, DefaultLocaleMessageSchema,Locale } from 'vue-i18n';
export type AllMessages = Readonly<${JSON.stringify(combinedMessages.messages)}>;
export type MessageSchemeType = AllMessages['en'];
export type AllTranslationKeys = TranslationsPaths<AllMessages> | '${(finalKeys ?? []).join("' | '")}';
export type MessageSchemaGen = MessageSchemeType & DefineLocaleMessage;
export type I18nMessages = AllMessages;
export type AllTranslations = I18nMessages;
export type MessagesType = I18nMessages;
export const messages: MessagesType;
export {messages as default };
}
declare module '${sourceId}/availableLocales' {
import type {PickupLocales } from "@intlify/core-base";
export type AvailableLocales = Readonly<[${availableLocales2.map((l) => `'${l}'`).join(", ")}]>
export type AvailableLocale = PickupLocales<AllMessages>
export const availableLocales : AvailableLocales;
export default availableLocales;
}
declare module '${sourceId}' {
import {type Plugin, type WritableComputedRef} from 'vue';
import { availableLocales } from '${sourceId}/availableLocales';
import type { Composer, Locale, FallbackLocale, ComposerOptions as Options, ComposerOptions, I18n, I18nOptions,DefaultDateTimeFormatSchema,DefaultNumberFormatSchema, NamedValue, TranslateOptions, UseI18nOptions} from "vue-i18n";
import type { MessagesType,AllMessages, AllTranslations,AllTranslationKeys,I18nMessages,MessageSchemaGen} from '${sourceId}/messages';
export type * from '${sourceId}/messages';
export type * from '${sourceId}/availableLocales';
import type { AvailableLocale,AvailableLocales} from '${sourceId}/availableLocales';
export interface I18nCustom {
(key: AllTranslationKeys, plural: number, options: TranslateOptions): string
(key: AllTranslationKeys): string
(key: AllTranslationKeys, options?: TranslateOptions): string
(key: AllTranslationKeys, defaultMsg?: string): string
(key: AllTranslationKeys, defaultMsg: string, options?: TranslateOptions): string
(key: AllTranslationKeys, named: NamedValue, defaultMsg?: string): string
(key: AllTranslationKeys, named: NamedValue, plural?: number): string
(key: AllTranslationKeys, named: NamedValue, options?: TranslateOptions & Record<string,string|number>): string
(key: AllTranslationKeys, plural: number| UnwrapRef<number>, named?: NamedValue<TranslateOptions>): string
(key: AllTranslationKeys, plural: number, defaultMsg?: string): string
};
declare module 'vue' {
import type {NamedValue, TranslateOptions} from "vue-i18n";
import type {AllTranslationKeys, I18nCustom} from 'virtual:vue-i18n-types';
/**
* Component Custom Properties for Vue I18n
*
* @VueI18nInjection
*/
export interface ComponentCustomProperties {
// $i18n: VueI18nInstance
$t(key: AllTranslationKeys, plural?: number|TranslateOptions|string|NamedValue, options?: TranslateOptions|string|number): string
}
}
export type DateTimeFormats = Record<AvailableLocale|string, DefaultDateTimeFormatSchema>
export type NumberFormats = Record<AvailableLocale|string, DefaultNumberFormatSchema>
export type I18nOptions = Omit<ComposerOptions,'messages'>&
{
messages:AllMessages,locale: ${combinedMessages.config.baseLocale},
};
export type CreateI18nOptions = Omit<ComposerOptions,'messages'>& I18nOptions &
{
lecacy: false,
dateTimeFormats?: DateTimeFormats,
numberFormats?: NumberFormats
};
// export type DateTimeFormats = DefaultDateTimeFormatSchema
// export type NumberFormats = DefaultNumberFormatSchema
export type I18InstanceType<Options extends CreateI18nOptions> = I18n<AllMessages,DateTimeFormats, NumberFormats, AvailableLocale,false>
export type UseI18nTypesafeReturn<TOptions extends I18nOptions=I18nOptions> = Omit<Composer<AllMessages, NonNullable<TOptions['datetimeFormats']>, NonNullable<TOptions['numberFormats']>, TOptions['locale'] extends unknown ?AvailableLocale: TOptions['locale']>,'t'> &
{
availableLocales: AvailableLocales
t: I18nCustom
tm: <TKey extends string |PickupPaths<MessageSchemaGen>,TResult extends ResourceValue<MessageSchemaGen, TKey>>(t: TKey) => TResult
locale: WritableComputedRef<AvailableLocale>
};
type I18nInstance<T extends Omit<ComposerOptions,'messages'>> = I18n<
MessagesType,
T['datetimeFormats'] extends Record<string, unknown> ? T['datetimeFormats'] : object,
T['numberFormats'] extends Record<string, unknown> ? T['numberFormats'] : object,
T['locale'] extends string ? T['locale'] : Locale,
false
>
export function createI18nInstance<Options extends CreateI18nOptions>(options?: Options): I18InstanceType<Options>;
export function createI18nInstancePlugin<T extends CreateI18nOptions>(options?: T):
Plugin<unknown[]>& I18nInstance<T>
export {fallbackLocales} from '${sourceId}/fallbackLocales'
export {availableLocales} from '${sourceId}/availableLocales'
export {messages} from '${sourceId}/messages'
export const useI18nApp: ()=> UseI18nTypesafeReturn
export function useI18nTypeSafe(options?:I18nOptions):UseI18nTypesafeReturn;
}
// declare module '@intlify/core-base' {
//
// export type DefineCoreLocaleMessage = _AllMessages;
// }
// import type { Composer, UseI18nOptions,DefaultLocaleMessageSchema, SchemaParams, LocaleParams, VueMessageType } from 'vue-i18n';
// declare module 'vue-i18n' {
// type messageScheme = ${JSON.stringify(baseLocaleMessages)}
// export type DefaultLocaleMessageSchema = _MessageSchemeType
// export type DefineLocaleMessage = _MessageSchemeType
// export type Locale = ${combinedMessages.languagesUnion()};
//export type ComposerTranslation
// }
declare module '${sourceId}/fallbackLocales' {
import type { Locale } from "vue-i18n";
export const fallbackLocales: { [locale in string]: Locale[];}
export default fallbackLocales;
}
declare module '${sourceId}/createI18nInstance' {
import type {createI18nInstance as ImportedType} from '${sourceId}';
export const createI18nInstance: ImportedType;
export default createI18nInstance;
}
declare module '${sourceId}/createI18nInstancePlugin' {
import type {createI18nInstancePlugin as ImportedType} from '${sourceId}';
export const createI18nInstancePlugin: ImportedType;
export default createI18nInstancePlugin;
}
declare module '${sourceId}/useI18nTypeSafe' {
export {useI18nTypeSafe} from '${sourceId}'
//export const useI18nTypeSafe: ImportedType
//export default useI18nTypeSafe;
}
// export {};
// declare module '@intlify/core-base' {
// export interface DefineCoreLocaleMessage {
// title: string
// menu: {
// login: string
// }
// }
// }
// declare module 'vue-i18n' {
//
// // import type { messages } from '${sourceId}/messages'
// interface DefineLocaleMessage {
// //(typeof messages)['de']
// test: string
// }
// export declare type I18nMode = 'composition'
//
// export = DefineLocaleMessage;
// export = x
// }
`
];
return (autogenerated + bodyLines.join(NL)).replace(/\r\n/g, NL).replace(/\r/g, NL).replace(/\n*$/, "\n");
}
function createVirtualModuleCode(messagesCombined) {
const messageMethods = new RuntimeMethods(messagesCombined);
return messageMethods;
}
// src/utils/hash.ts
function fnv1a32(input) {
let h = 2166136261;
for (let i = 0; i < input.length; i++) {
h ^= input.charCodeAt(i);
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
}
return ("00000000" + h.toString(16)).slice(-8);
}
async function ensureDir(filePath) {
const dir = path6__default.default.dirname(filePath);
await fs__default.default.mkdir(dir, { recursive: true });
}
async function writeFileAtomic(filePath, content) {
try {
await fs__default.default.writeFile(filePath, content, { encoding: "utf8", flush: true });
} catch (e) {
console.error("writeFileAtomic", e);
}
}
// src/core/combined-messages.ts
var import_picocolors = __toESM(require_picocolors());
var CombinedMessages = class _CombinedMessages {
constructor(messages3, config) {
this.messages = messages3;
this.config = config;
this.messagesJsonString = JSON.stringify(messages3);
const baseLocale = this.config.baseLocale || Object.keys(messages3)[0];
this.keys = getFinalKeys(messages3, baseLocale);
this.baseLocaleMessages = messages3?.[baseLocale] ?? Object.values(messages3)[0] ?? {};
const languages = Object.keys(messages3);
this.languages = Array.from(new Set(languages)).sort(
(a, b) => a < b ? -1 : a > b ? 1 : 0
);
this.contentId = fnv1a32(this.messagesJsonString);
this.keysId = fnv1a32(this.keys.join("|"));
this.fallbackLocales = _CombinedMessages.getFallBackLocales(this.languages);
}
messagesJsonString;
keys;
baseLocaleMessages;
languages;
contentId;
keysId;
fallbackLocales;
get typesOutpath() {
return path6__default.default.isAbsolute(this.config.typesPath) ? this.config.typesPath : path6__default.default.join(this.config.root, this.config.typesPath);
}
get virtualOutPath() {
return this.config.virtualFilePath ? path6__default.default.isAbsolute(this.config.virtualFilePath) ? this.config.virtualFilePath : path6__default.default.join(this.config.root, this.config.virtualFilePath) : void 0;
}
languagesTuple() {
return `['${this.languages.join(`', '`)}']`;
}
languagesUnion() {
return `'${this.languages.join(`' | '`)}'`;
}
async writeTypesFile() {
await ensureDir(this.typesOutpath);
this.config.logger.info(`Writing types file to: ${this.typesOutpath}`);
await writeFileAtomic(this.typesOutpath, this.getTypesContent());
}
async writeVirtualFile() {
const filePath = this.virtualOutPath;
if (!filePath) return;
await ensureDir(filePath);
this.config.logger.info(`Writing types file to: ${filePath}`);
await writeFileAtomic(filePath, this.getRuntimeContent());
}
static getFallBackLocales(langs) {
return langs.reduce((acc, locale) => {
acc[locale] = [locale, locale === "en" ? void 0 : "en", locale === "de" ? void 0 : "de"].filter((a) => a !== void 0);
if (locale === "en") acc[locale] = [...acc[locale], "en-US"];
return acc;
}, {});
}
getRuntimeContent() {
return this.getRuntimeMethods().toFileContent();
}
_runtimeMethodsCache = null;
getRuntimeMethods() {
if (this._runtimeMethodsCache) return this._runtimeMethodsCache;
this._runtimeMethodsCache = createVirtualModuleCode(this);
return this._runtimeMethodsCache;
}
loadVirtualModuleCode(moduleToResolve) {
const code = this.getRuntimeMethods();
if (moduleToResolve === "") {
this.config.logger.info(`\u{1F4C4} [${import_picocolors.default.green("load")}] Generated dev code for virtual module: ${moduleToResolve}`);
return code.toFileContent();
}
const methodCode = code.getFileContentFor(moduleToResolve);
this.config.logger.info(`\u{1F4C4} [${import_picocolors.default.green("load")}] Loading virtual sub-module: ${import_picocolors.default.yellow(moduleToResolve)}, length: ${methodCode.length}`);
return methodCode;
}
async writeFiles() {
const startWrite = performance.now();
await Promise.all([this.writeTypesFile(), this.writeVirtualFile()]);
const writeDuration = Math.round(performance.now() - startWrite);
this.config.logger.debug(`Generation took: ${writeDuration}ms`);
}
getTypesContent() {
const params = Object.assign({}, {
combinedMessages: this,
config: this.config
});
return toTypesContent(params);
}
/**
* Validate messages and log conflicts
*/
validateMessages() {
const conflicts = detectKeyConflicts(this.messages);
if (conflicts.length > 0) {
this.config.logger.error("\u26A0\uFE0F Conflicting translation keys detected:", {
timestamp: true
});
for (const conflict of conflicts) {
this.config.logger.error(` ${conflict}`, { timestamp: true });
}
}
}
};
// src/core/file-manager.ts
var FileManager = class {
constructor(options) {
this.options = options;
}
parsedFilesCache = /* @__PURE__ */ new Map();
// private grouped:Record<string, any> = {};
processedFiles = /* @__PURE__ */ new Set();
filesToProcess = /* @__PURE__ */ new Set();
async findFiles() {
const allfiles = await this.collectJsonFiles();
this.filesToProcess = new Set(allfiles);
}
async processFile(file) {
file ??= this.filesToProcess.values().next().value;
if (file) {
await this.readFile(file);
return file;
}
return null;
}
async fastGlob() {
const patterns = toArray(this.options.include);
const ignore = toArray(this.options.exclude).map((p) => p.startsWith("!") ? p.slice(1) : p);
const cwd = this.options.root;
const start = performance.now();
const { default: fg } = await import('fast-glob');
const list = await fg(patterns, {
cwd,
ignore,
absolute: true,
onlyFiles: true,
dot: false
});
const entries = list.map((p) => path6__default.default.normalize(p));
this.options.logger.info(`[FileManager] [findFiles] fast-glob: found ${entries.length} files in ${Math.round(performance.now() - start)}ms`);
return entries;
}
/**
* Collect JSON files using native Node.js glob or fast-glob fallback
*/
async collectJsonFiles() {
const patterns = toArray(this.options.include);
const ignore = toArray(this.options.exclude).map((p) => p.startsWith("!") ? p.slice(1) : p);
const cwd = this.options.root;
this.options.logger.debug(`[FileManager] collectJsonFiles: patterns=${patterns.join(", ")}`);
this.options.logger.debug(`[FileManager] collectJsonFiles: cwd=${cwd}`);
this.options.logger.debug(`[FileManager] collectJsonFiles: ignore=${ignore.join(", ")}`);
performance.now();
performance.now();
const fastGlobEntries = this.fastGlob();
const entries = /* @__PURE__ */ new Set([...await fastGlobEntries]);
const out = Array.from(entries);
this.options.logger.debug(`[FileManager] foundFiles: ${out.slice(0, Math.min(out.length, 10)).join(", ")}`);
const filteredFiles = out;
this.options.logger.info(`[FileManager] findFiles: total files found: ${out.length}/${filteredFiles.length} after filtering`);
filteredFiles.sort((a, b) => a.localeCompare(b));
return filteredFiles;
}
/**
* Read and group locale files with incremental updates
*/
async readAndGroup() {
const startReadGroup = performance.now();
if (this.filesToProcess.size === 0)
await this.findFiles();
const files = this.filesToProcess;
performance.now();
await this.readChangedFiles();
const totalDuration = Math.round(performance.now() - startReadGroup);
this.options.logger.info(`[FileManager] reading ${this.processedFiles.size} files completed in ${totalDuration}ms`);
const combinedMessages = await this.buildMessagesAndNotify(files);
return {
messages: combinedMessages,
stats: {
durations: {
stat: totalDuration,
read: totalDuration,
total: totalDuration
}
}
};
}
async buildMessagesAndNotify(files) {
const combinedMessages = new CombinedMessages(this.getGrouped(), this.options);
await Promise.all(this.callbacks.map((cb) => cb(combinedMessages, [...files])));
return combinedMessages;
}
getGrouped(files) {
const grouped = {};
files ??= this.parsedFilesCache;
for (const [_, { localeKey, prepared }] of files) {
if (!(localeKey in grouped)) grouped[localeKey] = {};
grouped[localeKey] = this.options.mergeFunction(grouped[localeKey], prepared);
}
return canonicalize(grouped);
}
/**
* Read and parse changed files
*/
async readChangedFiles() {
let readFiles = 0;
const filesToProcess = /* @__PURE__ */ new Set([...this.filesToProcess]);
const totalFiles = filesToProcess.size;
const start = performance.now();
while (filesToProcess.size > 0) {
const readFileBatches = this.options.fileBatchSize ?? 100;
const filesToProcessInRound = [...filesToProcess].slice(0, Math.min(readFileBatches, filesToProcess.size));
this.options.logger.debug(`[FileManager] readChangedFiles: processing batch of ${readFiles} / ${totalFiles} files. | Elapsed: ${Math.round(performance.now() - start)}ms`);
const tasks = filesToProcessInRound.map((a) => this.processFile(a));
filesToProcessInRound.forEach((a) => filesToProcess.delete(a));
await Promise.all(tasks);
filesToProcessInRound.forEach((file) => this.filesToProcess.delete(file));
filesToProcessInRound.forEach((file) => this.processedFiles.add(file));
readFiles += filesToProcessInRound.length;
}
}
async readFile(abs, readFileContent, localeKey = null) {
localeKey ??= this.options.getLocaleFromPath(abs, this.options.root);
if (!localeKey) {
this.options.logger?.warn(`Skipping file with invalid locale: ${abs}`);
return;
}
if (localeKey.length !== 2 && localeKey.length !== 5) {
this.options.logger?.warn(`Uncommon locale: ${abs} -> ${localeKey}`);
}
try {
readFileContent ??= () => fs$1.promises.readFile(abs, "utf8");
const raw = await readFileContent();
const parsed = parseJSONWithLocation(raw, abs);
const prepared = this.options.transformJson ? this.options.transformJson(parsed, abs) : parsed;
this.parsedFilesCache.set(abs, { localeKey, prepared });
return { localeKey, prepared };
} catch (err) {
const formattedError = wrapErrorWithFile(abs, err);
this.options.logger?.error(formattedError.message);
throw formattedError;
}
}
/**
* Get list of last processed files
*/
getLastFiles() {
return [...this.processedFiles];
}
/**
* Clear all caches
*/
clearCache() {
this.parsedFilesCache.clear();
this.processedFiles = /* @__PURE__ */ new Set();
this.filesToProcess = /* @__PURE__ */ new Set();
}
async fileUpdated(filePath, readFile, locale = null) {
this.parsedFilesCache.delete(filePath);
this.filesToProcess.add(filePath);
const newVar = await this.readFile(filePath);
await this.buildMessagesAndNotify([filePath]);
return newVar;
}
fileUpdatedWithLocale(filePath, locale) {
return this.fileUpdated(filePath, void 0, locale);
}
validateMessages() {
return new Promise((resolve) => {
const conflicts = detectKeyConflicts(this.getGrouped());
if (conflicts.length > 0) {
this.options.logger.error("\u26A0\uFE0F Conflicting translation keys detected:", {
timestamp: true
});
for (const conflict of conflicts) {
this.options.logger.error(` ${conflict}`, { timestamp: true });
}
}
resolve(conflicts);
});
}
async fileRemoved(filePath) {
this.parsedFilesCache.delete(filePath);
await this.buildMessagesAndNotify([filePath]);
}
addNewFile(filePath, readFile, mtime) {
return this.fileUpdated(filePath, readFile);
}
callbacksFileChanged = [];
callbacks = [];
addOnAllFilesProcessed(callback, _priority = 0) {
if (_priority > 0) {
this.callbacks.unshift(callback);
return;
} else {
this.callbacks.push(callback);
}
}
addOnFileChanged(callback) {
this.callbacksFileChanged.push(callback);
}
};
// src/createConsoleLogger.ts
var import_picocolors2 = __toESM(require_picocolors());
function createConsoleLogger(debugEnabled = false, prefix = ` i18n-typescript`) {
const warnedMessages = /* @__PURE__ */ new Set();
return {
info: (msg) => {
if (debugEnabled) {
console.log(`[${import_picocolors2.default.dim(prefix)}] ${msg}`);
}
},
warn: (msg) => {
console.warn(`[${import_picocolors2.default.dim(prefix)}] ${msg}`);
},
error: (msg) => {
console.error(`[${import_picocolors2.default.dim(prefix)}] ${msg}`);
},
warnOnce: (msg) => {
if (!warnedMessages.has(msg)) {
warnedMessages.add(msg);
console.warn(`[${import_picocolors2.default.dim(prefix)}] ${msg}`);
}
},
clearScreen: () => {
},
hasErrorLogged: () => false,
hasWarned: false
};
}
function createColoredLogger(level, options) {
const logger = options?.customLogger ?? createConsoleLogger(true, options?.prefix);
const prefix = options?.prefix ?? `vue-i18n-typescript`;
return {
debug: (msg, options2) => {
if (level !== "debug") return;
logger.info(`${import_picocolors2.default.dim(import_picocolors2.default.cyanBright(prefix))} ${import_picocolors2.default.gray(msg)}`, options2);
},
info: (msg, options2) => {
logger.info(`${import_picocolors2.default.dim(import_picocolors2.default.blueBright(prefix))} ${msg}`, options2);
},
warn: (msg, options2) => {
logger.warn(`${import_picocolors2.default.dim(prefix)} ${import_picocolors2.default.yellow(msg)}`, options2);
},
error: (msg, options2) => {
logger.clearScreen("info");
logger.error(`${import_picocolors2.default.bold(prefix)} ${import_picocolors2.default.red(msg)}`, options2);
},
warnOnce: (msg, options2) => {
logger.clearScreen("info");
logger.warnOnce(`${import_picocolors2.default.bold(prefix)} ${import_picocolors2.default.yellow(msg)}`, options2);
},
clearScreen: (type) => {
logger.clearScreen(type);
},
hasErrorLogged: (error) => logger.hasErrorLogged(error),
hasWarned: logger.hasWarned
};
}
// src/api.ts
async function generateI18nTypes(options = {}) {
const root = options.root ? path6__default.default.resolve(options.root) : process.cwd();
const debugEnabled = options.debug ?? options.verbose ?? false;
const logger = createColoredLogger(options.debug ? "debug" : "info");
const config = normalizeConfig({
...options,
debug: debugEnabled
}, logger);
logger.info(`Starting type generation in: ${root}`);
logger.info(`Base locale: ${config.baseLocale}`);
logger.info(`Include patterns: ${config.include.join(", ")}`);
logger.info(`Types output: ${config.typesPath}`);
const fileManager = new FileManager(config);
let lastFiles = [];
const startTime = performance.now();
const result = await fileManager.readAndGroup();
lastFiles = fileManager.getLastFiles();
const locales = result.messages.languages.filter((l) => l !== "js-reserved");
const writeStartTime = performance.now();
await result.messages.writeFiles();
const writeDuration = Math.round(performance.now() - writeStartTime);
const totalDuration = Math.round(performance.now() - startTime);
logger.info(`\u2705 Generated types for ${locales.length} locale(s): ${locales.join(", ")}`);
logger.info(`\u{1F4C1} Processed ${lastFiles.length} locale file(s)`);
const typesPath = path6__default.default.isAbsolute(config.typesPath) ? config.typesPath : path6__default.default.join(root, config.typesPath);
const generatedFiles = [path6__default.default.relative(root, typesPath).replace(/\\/g, "/")];
let filesWritten = 1;
if (config.virtualFilePath) {
const virtualPath = path6__default.default.isAbsolute(config.virtualFilePath) ? config.virtualFilePath : path6__default.default.join(root, config.virtualFilePath);
generatedFiles.push(path6__default.default.relative(root, virtualPath).replace(/\\/g, "/"));
filesWritten = 2;
}
return {
filesWritten,
totalFiles: generatedFiles.length,
generatedFiles,
durations: {
content: totalDuration - writeDuration,
write: writeDuration,
total: totalDuration
},
locales,
localeFilesCount: lastFiles.length,
localeFiles: lastFiles
};
}
//!['messages', 'availableLocales', 'fallbackLocales', 'useI18nApp', 'translationWrapper', 'useI18nTypeSafe', 'createI18nInstance', 'createI18nInstancePlugin'].includes(target);
exports.CombinedMessages = CombinedMessages;
exports.createColoredLogger = createColoredLogger;
exports.createConsoleLogger = createConsoleLogger;
exports.generateI18nTypes = generateI18nTypes;
exports.normalizeConfig = normalizeConfig;
//# sourceMappingURL=api.cjs.map
//# sourceMappingURL=api.cjs.map