UNPKG

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
'use strict'; 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