UNPKG

@shelchin/svelte-i18n

Version:

The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.

150 lines (149 loc) 5.15 kB
/** * Type discovery utilities for third-party packages * Automatically finds and loads i18n types from packages */ import { existsSync, readFileSync } from 'fs'; /** * Try to discover i18n types from a package * @param packageName - Name of the package to check * @returns The I18N_PATHS array if found, or null */ export async function discoverPackageTypes(packageName) { try { // Try standard export path first const typesModule = await import(`${packageName}/i18n-types`); if (typesModule.I18N_PATHS) { console.log(` ✅ Found types from ${packageName}/i18n-types`); return typesModule.I18N_PATHS; } } catch { // Try alternative paths } // Try to find package.json and check exports try { const packageJsonPath = require.resolve(`${packageName}/package.json`); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); // Check if package has i18n-types export if (packageJson.exports?.['./i18n-types']) { const typesPath = packageJson.exports['./i18n-types']; console.log(` Found i18n-types export in package.json: ${typesPath}`); } } catch { // Package not found or no exports } return null; } /** * Get validation schema for a package * First tries to get TypeScript types, then falls back to JSON schema */ export async function getPackageSchema(packageName) { // Try to get TypeScript types first const paths = await discoverPackageTypes(packageName); if (paths) { return { paths, source: `TypeScript types from ${packageName}/i18n-types` }; } // Fall back to JSON schema from default language file const possiblePaths = [ `node_modules/${packageName}/dist/translations/en.json`, `node_modules/${packageName}/translations/en.json`, `src/translations/${packageName}/en.json` ]; for (const schemaPath of possiblePaths) { if (existsSync(schemaPath)) { const content = readFileSync(schemaPath, 'utf-8'); const schema = JSON.parse(content); console.log(` Using JSON schema from ${schemaPath}`); return { schema, source: `JSON schema from ${schemaPath}` }; } } return null; } /** * Validate translations against package types or schema */ export function validateAgainstPackage(translations, packageInfo) { const errors = []; if (packageInfo.paths) { // Validate against TypeScript paths const translationPaths = extractPaths(translations); // Check for missing paths for (const requiredPath of packageInfo.paths) { if (!translationPaths.includes(requiredPath)) { errors.push(`Missing key: ${requiredPath}`); } } // Check for extra paths (warnings) for (const path of translationPaths) { if (!packageInfo.paths.includes(path)) { errors.push(`Extra key (warning): ${path}`); } } } else if (packageInfo.schema) { // Use existing schema validation return validateStructure(packageInfo.schema, translations); } return errors; } /** * Extract all paths from a translation object */ function extractPaths(obj, prefix = '') { const paths = []; for (const [key, value] of Object.entries(obj)) { if (key === '_meta') continue; const fullPath = prefix ? `${prefix}.${key}` : key; if (typeof value === 'string') { paths.push(fullPath); } else if (typeof value === 'object' && value !== null) { paths.push(...extractPaths(value, fullPath)); } } return paths; } /** * Validate structure (existing function, for reference) */ function validateStructure(source, target, path = '', errors = []) { // Check missing keys for (const key in source) { if (key === '_meta') continue; const fullPath = path ? `${path}.${key}` : key; if (!(key in target)) { errors.push(`Missing key: ${fullPath}`); } else if (typeof source[key] === 'object' && source[key] !== null) { if (typeof target[key] !== 'object' || target[key] === null) { errors.push(`Type mismatch at ${fullPath}: expected object, got ${typeof target[key]}`); } else { validateStructure(source[key], target[key], fullPath, errors); } } else if (typeof source[key] !== typeof target[key]) { errors.push(`Type mismatch at ${fullPath}: expected ${typeof source[key]}, got ${typeof target[key]}`); } } // Check extra keys (warnings) for (const key in target) { if (key === '_meta') continue; if (!(key in source)) { const fullPath = path ? `${path}.${key}` : key; errors.push(`Extra key (warning): ${fullPath}`); } } return errors; }