UNPKG

zlocalz

Version:

ZLocalz - TUI Locale Guardian for Flutter ARB l10n/i18n validation and translation with AI-powered fixes

367 lines 14.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UniversalParser = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const fast_glob_1 = __importDefault(require("fast-glob")); const YAML = __importStar(require("yaml")); const sync_1 = require("csv-parse/sync"); const sync_2 = require("csv-stringify/sync"); class UniversalParser { config; constructor(config) { this.config = config; } static detectFormat(filePath) { const ext = path.extname(filePath).toLowerCase(); switch (ext) { case '.arb': return 'arb'; case '.json': return 'json'; case '.yaml': case '.yml': return 'yaml'; case '.csv': return 'csv'; case '.tsv': return 'tsv'; default: return 'json'; } } static getFilePatterns(format) { switch (format) { case 'arb': return ['**/*.arb']; case 'json': return ['**/*.json']; case 'yaml': return ['**/*.yaml', '**/*.yml']; case 'csv': return ['**/*.csv']; case 'tsv': return ['**/*.tsv']; case 'auto': default: return ['**/*.arb', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.csv', '**/*.tsv']; } } async discoverFiles(basePath) { const format = this.config.fileFormat || 'auto'; const patterns = this.config.filePattern ? [this.config.filePattern] : UniversalParser.getFilePatterns(format); const files = await (0, fast_glob_1.default)(patterns, { cwd: basePath, absolute: true, ignore: ['**/node_modules/**', '**/build/**', '**/.*/**'] }); return files.sort(); } async parseAllLocalesFromFile(filePath) { const format = UniversalParser.detectFormat(filePath); if (format === 'csv' || format === 'tsv') { const content = await fs.readFile(filePath, 'utf-8'); const delimiter = format === 'tsv' ? '\t' : (this.config.csvOptions?.delimiter || ','); const records = (0, sync_1.parse)(content, { delimiter, columns: true, skip_empty_lines: true }); const locales = []; const allLocales = new Set(); if (records.length > 0) { const columns = Object.keys(records[0]); for (const column of columns) { if (column !== (this.config.csvOptions?.keyColumn || 'key') && column !== 'description' && column !== 'context') { allLocales.add(column); } } } for (const locale of allLocales) { if (this.config.sourceLocale === locale || this.config.targetLocales.includes(locale)) { const entries = this.parseCsvEntries(records, locale); const stats = await fs.stat(filePath); locales.push({ locale, path: filePath, format, entries, raw: { records, locale }, lastModified: stats.mtime }); } } return locales; } else { const localeFile = await this.parseFile(filePath); return [localeFile]; } } async parseFile(filePath, targetLocale) { const format = UniversalParser.detectFormat(filePath); const content = await fs.readFile(filePath, 'utf-8'); const locale = targetLocale || this.extractLocale(filePath, format); let raw; let entries = {}; switch (format) { case 'arb': case 'json': raw = JSON.parse(content); entries = this.parseJsonEntries(raw); break; case 'yaml': raw = YAML.parse(content) || {}; entries = this.parseYamlEntries(raw, locale); break; case 'csv': case 'tsv': const delimiter = format === 'tsv' ? '\t' : (this.config.csvOptions?.delimiter || ','); const records = (0, sync_1.parse)(content, { delimiter, columns: true, skip_empty_lines: true }); raw = { records, locale }; entries = this.parseCsvEntries(records, locale); break; default: throw new Error(`Unsupported file format: ${format}`); } const stats = await fs.stat(filePath); return { locale, path: filePath, format, entries, raw, lastModified: stats.mtime }; } parseJsonEntries(raw) { const entries = {}; const metadataPrefix = '@'; for (const [key, value] of Object.entries(raw)) { if (key.startsWith(metadataPrefix)) continue; if (key.startsWith('@@')) continue; const entry = { key, value: String(value) }; const metadataKey = `${metadataPrefix}${key}`; if (raw[metadataKey]) { const metadata = raw[metadataKey]; entry.metadata = metadata; entry.description = metadata.description; entry.placeholders = metadata.placeholders; } entries[key] = entry; } return entries; } parseYamlEntries(raw, _locale) { const entries = {}; const flatten = (obj, prefix = '') => { for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; if (typeof value === 'object' && value !== null && !Array.isArray(value)) { const valueObj = value; if (valueObj.value !== undefined) { entries[fullKey] = { key: fullKey, value: String(valueObj.value), description: valueObj.description, context: valueObj.context, tags: valueObj.tags, metadata: valueObj }; } else { flatten(value, fullKey); } } else { entries[fullKey] = { key: fullKey, value: String(value) }; } } }; flatten(raw); return entries; } parseCsvEntries(records, locale) { const entries = {}; const keyColumn = this.config.csvOptions?.keyColumn || 'key'; const valueColumns = this.config.csvOptions?.valueColumns || { [locale]: locale }; const valueColumn = valueColumns[locale] || locale; for (const record of records) { const key = record[keyColumn]; const value = record[valueColumn]; if (key && value) { entries[key] = { key, value: String(value), description: record.description, context: record.context, tags: record.tags ? record.tags.split(',').map((t) => t.trim()) : undefined, metadata: record }; } } return entries; } async writeFile(localeFile) { let content; switch (localeFile.format) { case 'arb': case 'json': content = this.generateJsonContent(localeFile); break; case 'yaml': content = this.generateYamlContent(localeFile); break; case 'csv': case 'tsv': content = this.generateCsvContent(localeFile); break; default: throw new Error(`Unsupported file format: ${localeFile.format}`); } await fs.writeFile(localeFile.path, content, 'utf-8'); } generateJsonContent(localeFile) { const output = {}; if (localeFile.format === 'arb' && localeFile.raw['@@locale']) { output['@@locale'] = localeFile.raw['@@locale']; } const keys = Object.keys(localeFile.entries); if (this.config.preferOrder === 'alphabetical') { keys.sort(); } for (const key of keys) { const entry = localeFile.entries[key]; output[key] = entry.value; if (localeFile.format === 'arb' && (entry.metadata || entry.description || entry.placeholders)) { const metadata = {}; if (entry.description) metadata.description = entry.description; if (entry.placeholders) metadata.placeholders = entry.placeholders; if (entry.metadata) Object.assign(metadata, entry.metadata); if (Object.keys(metadata).length > 0) { output[`@${key}`] = metadata; } } } return JSON.stringify(output, null, 2) + '\n'; } generateYamlContent(localeFile) { const output = {}; for (const [key, entry] of Object.entries(localeFile.entries)) { const keyParts = key.split('.'); let current = output; for (let i = 0; i < keyParts.length - 1; i++) { if (!current[keyParts[i]]) { current[keyParts[i]] = {}; } current = current[keyParts[i]]; } const finalKey = keyParts[keyParts.length - 1]; if (entry.description || entry.context || entry.tags || entry.metadata) { current[finalKey] = { value: entry.value, ...(entry.description && { description: entry.description }), ...(entry.context && { context: entry.context }), ...(entry.tags && { tags: entry.tags }) }; } else { current[finalKey] = entry.value; } } return YAML.stringify(output, { indent: 2 }); } generateCsvContent(localeFile) { const delimiter = localeFile.format === 'tsv' ? '\t' : (this.config.csvOptions?.delimiter || ','); const keyColumn = this.config.csvOptions?.keyColumn || 'key'; const valueColumn = this.config.csvOptions?.valueColumns?.[localeFile.locale] || localeFile.locale; const records = Object.values(localeFile.entries).map(entry => ({ [keyColumn]: entry.key, [valueColumn]: entry.value, ...(entry.description && { description: entry.description }), ...(entry.context && { context: entry.context }), ...(entry.tags && { tags: entry.tags.join(', ') }) })); return (0, sync_2.stringify)(records, { delimiter, header: true, quoted_string: true }); } extractLocale(filePath, _format) { const basename = path.basename(filePath, path.extname(filePath)); const patterns = [ /^(.+)_([a-z]{2}(?:_[A-Z]{2})?)$/, /^([a-z]{2}(?:[-_][A-Z]{2})?)$/, /^([a-z]{2}(?:[-_][A-Z]{2})?)[\\/](.+)$/ ]; for (const pattern of patterns) { const match = basename.match(pattern); if (match) { return match[match.length - 1].replace('-', '_'); } } const parentDir = path.basename(path.dirname(filePath)); if (/^[a-z]{2}(?:[-_][A-Z]{2})?$/.test(parentDir)) { return parentDir.replace('-', '_'); } return 'unknown'; } } exports.UniversalParser = UniversalParser; //# sourceMappingURL=universal-parser.js.map