poly-lexis 0.9.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,85 @@
2
2
  #!/usr/bin/env node
3
3
 
4
4
  // src/translations/cli/validate.ts
5
- import * as path3 from "path";
5
+ import * as path4 from "path";
6
6
 
7
- // src/translations/utils/utils.ts
7
+ // src/translations/utils/metadata.ts
8
+ import { createHash } from "crypto";
8
9
  import * as fs from "fs";
9
10
  import * as path from "path";
11
+ var METADATA_FILE_NAME = ".translations-meta.json";
12
+ var CURRENT_VERSION = 1;
13
+ function emptyMetadata() {
14
+ return { version: CURRENT_VERSION, languages: {} };
15
+ }
16
+ function hashSourceValue(value) {
17
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
18
+ }
19
+ function getMetadataPath(translationsPath) {
20
+ return path.join(translationsPath, METADATA_FILE_NAME);
21
+ }
22
+ function metadataExists(translationsPath) {
23
+ return fs.existsSync(getMetadataPath(translationsPath));
24
+ }
25
+ function readMetadata(translationsPath) {
26
+ const metadataPath = getMetadataPath(translationsPath);
27
+ if (!fs.existsSync(metadataPath)) {
28
+ return emptyMetadata();
29
+ }
30
+ try {
31
+ const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
32
+ return {
33
+ version: parsed.version ?? CURRENT_VERSION,
34
+ languages: parsed.languages ?? {}
35
+ };
36
+ } catch {
37
+ return emptyMetadata();
38
+ }
39
+ }
40
+ function writeMetadata(translationsPath, metadata) {
41
+ if (!fs.existsSync(translationsPath)) {
42
+ fs.mkdirSync(translationsPath, { recursive: true });
43
+ }
44
+ const metadataPath = getMetadataPath(translationsPath);
45
+ fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
46
+ `, "utf-8");
47
+ }
48
+ function getSourceHash(metadata, language, namespace, key) {
49
+ return metadata.languages[language]?.[namespace]?.[key];
50
+ }
51
+ function pruneMetadata(metadata, sourceKeysByNamespace) {
52
+ let changed = false;
53
+ for (const language of Object.keys(metadata.languages)) {
54
+ const namespaces = metadata.languages[language];
55
+ for (const namespace of Object.keys(namespaces)) {
56
+ const sourceKeys = sourceKeysByNamespace[namespace];
57
+ if (!sourceKeys) {
58
+ delete namespaces[namespace];
59
+ changed = true;
60
+ continue;
61
+ }
62
+ for (const key of Object.keys(namespaces[namespace])) {
63
+ if (!sourceKeys.has(key)) {
64
+ delete namespaces[namespace][key];
65
+ changed = true;
66
+ }
67
+ }
68
+ if (Object.keys(namespaces[namespace]).length === 0) {
69
+ delete namespaces[namespace];
70
+ changed = true;
71
+ }
72
+ }
73
+ if (Object.keys(namespaces).length === 0) {
74
+ delete metadata.languages[language];
75
+ changed = true;
76
+ }
77
+ }
78
+ return changed;
79
+ }
80
+
81
+ // src/translations/utils/utils.ts
82
+ import * as fs2 from "fs";
83
+ import * as path2 from "path";
10
84
  function flattenObject(obj, prefix = "") {
11
85
  const result = {};
12
86
  for (const [key, value] of Object.entries(obj)) {
@@ -23,16 +97,16 @@ function isNestedObject(obj) {
23
97
  return Object.values(obj).some((value) => typeof value === "object" && value !== null);
24
98
  }
25
99
  function readTranslations(translationsPath, language) {
26
- const langPath = path.join(translationsPath, language);
27
- if (!fs.existsSync(langPath)) {
100
+ const langPath = path2.join(translationsPath, language);
101
+ if (!fs2.existsSync(langPath)) {
28
102
  return {};
29
103
  }
30
- const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
104
+ const files = fs2.readdirSync(langPath).filter((f) => f.endsWith(".json"));
31
105
  const translations = {};
32
106
  for (const file of files) {
33
- const namespace = path.basename(file, ".json");
34
- const filePath = path.join(langPath, file);
35
- const content = fs.readFileSync(filePath, "utf-8");
107
+ const namespace = path2.basename(file, ".json");
108
+ const filePath = path2.join(langPath, file);
109
+ const content = fs2.readFileSync(filePath, "utf-8");
36
110
  const parsed = JSON.parse(content);
37
111
  if (isNestedObject(parsed)) {
38
112
  translations[namespace] = flattenObject(parsed);
@@ -43,35 +117,35 @@ function readTranslations(translationsPath, language) {
43
117
  return translations;
44
118
  }
45
119
  function writeTranslation(translationsPath, language, namespace, translations) {
46
- const langPath = path.join(translationsPath, language);
47
- if (!fs.existsSync(langPath)) {
48
- fs.mkdirSync(langPath, { recursive: true });
120
+ const langPath = path2.join(translationsPath, language);
121
+ if (!fs2.existsSync(langPath)) {
122
+ fs2.mkdirSync(langPath, { recursive: true });
49
123
  }
50
- const filePath = path.join(langPath, `${namespace}.json`);
51
- fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
124
+ const filePath = path2.join(langPath, `${namespace}.json`);
125
+ fs2.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
52
126
  `, "utf-8");
53
127
  }
54
128
  function getAvailableLanguages(translationsPath) {
55
- if (!fs.existsSync(translationsPath)) {
129
+ if (!fs2.existsSync(translationsPath)) {
56
130
  return [];
57
131
  }
58
- return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
132
+ return fs2.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
59
133
  }
60
134
  function getNamespaces(translationsPath, language) {
61
- const langPath = path.join(translationsPath, language);
62
- if (!fs.existsSync(langPath)) {
135
+ const langPath = path2.join(translationsPath, language);
136
+ if (!fs2.existsSync(langPath)) {
63
137
  return [];
64
138
  }
65
- return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path.basename(f, ".json"));
139
+ return fs2.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
66
140
  }
67
141
  function ensureTranslationsStructure(translationsPath, languages) {
68
- if (!fs.existsSync(translationsPath)) {
69
- fs.mkdirSync(translationsPath, { recursive: true });
142
+ if (!fs2.existsSync(translationsPath)) {
143
+ fs2.mkdirSync(translationsPath, { recursive: true });
70
144
  }
71
145
  for (const lang of languages) {
72
- const langPath = path.join(translationsPath, lang);
73
- if (!fs.existsSync(langPath)) {
74
- fs.mkdirSync(langPath, { recursive: true });
146
+ const langPath = path2.join(translationsPath, lang);
147
+ if (!fs2.existsSync(langPath)) {
148
+ fs2.mkdirSync(langPath, { recursive: true });
75
149
  }
76
150
  }
77
151
  }
@@ -102,8 +176,8 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
102
176
  const targetNamespaces = getNamespaces(translationsPath, language);
103
177
  for (const namespace of targetNamespaces) {
104
178
  if (!sourceNamespaces.includes(namespace)) {
105
- const filePath = path.join(translationsPath, language, `${namespace}.json`);
106
- fs.unlinkSync(filePath);
179
+ const filePath = path2.join(translationsPath, language, `${namespace}.json`);
180
+ fs2.unlinkSync(filePath);
107
181
  result.removedNamespaces.push({
108
182
  language,
109
183
  namespace,
@@ -112,9 +186,9 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
112
186
  }
113
187
  }
114
188
  for (const namespace of sourceNamespaces) {
115
- const filePath = path.join(translationsPath, language, `${namespace}.json`);
189
+ const filePath = path2.join(translationsPath, language, `${namespace}.json`);
116
190
  const sourceFile = sourceTranslations[namespace] || {};
117
- if (fs.existsSync(filePath)) {
191
+ if (fs2.existsSync(filePath)) {
118
192
  const targetFile = targetTranslations[namespace] || {};
119
193
  let hasOrphanedKeys = false;
120
194
  const cleanedFile = {};
@@ -154,12 +228,22 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
154
228
  });
155
229
  }
156
230
  }
231
+ if (metadataExists(translationsPath)) {
232
+ const sourceKeysByNamespace = {};
233
+ for (const namespace of sourceNamespaces) {
234
+ sourceKeysByNamespace[namespace] = new Set(Object.keys(sourceTranslations[namespace] || {}));
235
+ }
236
+ const metadata = readMetadata(translationsPath);
237
+ if (pruneMetadata(metadata, sourceKeysByNamespace)) {
238
+ writeMetadata(translationsPath, metadata);
239
+ }
240
+ }
157
241
  return result;
158
242
  }
159
243
 
160
244
  // src/translations/cli/init.ts
161
- import * as fs2 from "fs";
162
- import * as path2 from "path";
245
+ import * as fs3 from "fs";
246
+ import * as path3 from "path";
163
247
 
164
248
  // src/translations/core/schema.ts
165
249
  var DEEPL_LANGUAGES = [
@@ -499,8 +583,8 @@ var DEFAULT_CONFIG = {
499
583
  function detectExistingTranslations(projectRoot) {
500
584
  const possiblePaths = ["public/static/locales", "public/locales", "src/locales", "locales", "i18n", "translations"];
501
585
  for (const possiblePath of possiblePaths) {
502
- const fullPath = path2.join(projectRoot, possiblePath);
503
- if (fs2.existsSync(fullPath)) {
586
+ const fullPath = path3.join(projectRoot, possiblePath);
587
+ if (fs3.existsSync(fullPath)) {
504
588
  const languages = getAvailableLanguages(fullPath);
505
589
  if (languages.length > 0) {
506
590
  return { path: possiblePath, languages };
@@ -510,8 +594,8 @@ function detectExistingTranslations(projectRoot) {
510
594
  return { path: null, languages: [] };
511
595
  }
512
596
  function loadConfig(projectRoot) {
513
- const configPath = path2.join(projectRoot, ".translationsrc.json");
514
- if (!fs2.existsSync(configPath)) {
597
+ const configPath = path3.join(projectRoot, ".translationsrc.json");
598
+ if (!fs3.existsSync(configPath)) {
515
599
  const existing = detectExistingTranslations(projectRoot);
516
600
  if (existing.path && existing.languages.length > 0) {
517
601
  console.log(`\u2139\uFE0F No config found, but detected translations at ${existing.path}`);
@@ -523,7 +607,7 @@ function loadConfig(projectRoot) {
523
607
  }
524
608
  return DEFAULT_CONFIG;
525
609
  }
526
- const configContent = fs2.readFileSync(configPath, "utf-8");
610
+ const configContent = fs3.readFileSync(configPath, "utf-8");
527
611
  const config = JSON.parse(configContent);
528
612
  if (config.languages) {
529
613
  const validation = validateLanguages(config.languages);
@@ -538,13 +622,15 @@ function loadConfig(projectRoot) {
538
622
  // src/translations/cli/validate.ts
539
623
  function validateTranslations(projectRoot = process.cwd()) {
540
624
  const config = loadConfig(projectRoot);
541
- const translationsPath = path3.join(projectRoot, config.translationsPath);
625
+ const translationsPath = path4.join(projectRoot, config.translationsPath);
542
626
  const sourceLanguage = config.sourceLanguage;
543
627
  const missing = [];
544
628
  const empty = [];
545
629
  const orphaned = [];
630
+ const stale = [];
546
631
  const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
547
632
  const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
633
+ const metadata = readMetadata(translationsPath);
548
634
  const languages = config.languages.filter((lang) => lang !== sourceLanguage);
549
635
  const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
550
636
  if (syncResult.createdFiles.length > 0) {
@@ -581,6 +667,17 @@ function validateTranslations(projectRoot = process.cwd()) {
581
667
  language,
582
668
  sourceValue
583
669
  });
670
+ } else if (typeof targetValue === "string") {
671
+ const recordedHash = getSourceHash(metadata, language, namespace, key);
672
+ if (recordedHash !== void 0 && recordedHash !== hashSourceValue(sourceValue)) {
673
+ stale.push({
674
+ namespace,
675
+ key,
676
+ language,
677
+ sourceValue,
678
+ currentValue: targetValue
679
+ });
680
+ }
584
681
  }
585
682
  }
586
683
  for (const [key, targetValue] of Object.entries(targetKeys)) {
@@ -596,7 +693,7 @@ function validateTranslations(projectRoot = process.cwd()) {
596
693
  }
597
694
  }
598
695
  const valid = !missing.length && !empty.length && !orphaned.length;
599
- if (valid) {
696
+ if (valid && !stale.length) {
600
697
  console.log("\u2713 All translations are valid!");
601
698
  } else {
602
699
  if (missing.length > 0) {
@@ -629,9 +726,22 @@ function validateTranslations(projectRoot = process.cwd()) {
629
726
  console.log(` ... and ${orphaned.length - 10} more`);
630
727
  }
631
728
  }
729
+ if (stale.length > 0) {
730
+ console.log(
731
+ `
732
+ \u26A0 Found ${stale.length} potentially outdated translations (source value changed since translation):`
733
+ );
734
+ for (const item of stale.slice(0, 10)) {
735
+ console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
736
+ }
737
+ if (stale.length > 10) {
738
+ console.log(` ... and ${stale.length - 10} more`);
739
+ }
740
+ console.log(" Run auto-fill with --retranslate-changed to update them.");
741
+ }
632
742
  }
633
743
  console.log("=====");
634
- return { valid, missing, empty, orphaned };
744
+ return { valid, missing, empty, orphaned, stale };
635
745
  }
636
746
 
637
747
  // src/scripts/verify-translations.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/translations/cli/validate.ts","../../src/translations/utils/utils.ts","../../src/translations/cli/init.ts","../../src/translations/core/schema.ts","../../src/translations/core/types.ts","../../src/scripts/verify-translations.ts"],"sourcesContent":["import * as path from 'node:path';\nimport type { MissingTranslation, OrphanedTranslation, ValidationResult } from '../core/types.js';\nimport { getNamespaces, readTranslations, syncTranslationStructure } from '../utils/utils.js';\nimport { loadConfig } from './init.js';\n\n/**\n * Validate all translations against the source language\n * Checks for missing keys, empty values, and orphaned keys (keys removed from source)\n */\nexport function validateTranslations(projectRoot: string = process.cwd()): ValidationResult {\n const config = loadConfig(projectRoot);\n const translationsPath = path.join(projectRoot, config.translationsPath);\n const sourceLanguage = config.sourceLanguage;\n\n const missing: MissingTranslation[] = [];\n const empty: MissingTranslation[] = [];\n const orphaned: OrphanedTranslation[] = [];\n\n // Read source translations\n const sourceTranslations = readTranslations(translationsPath, sourceLanguage);\n const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);\n\n // CRITICAL: Use config languages instead of filesystem languages\n // This ensures we validate ALL configured languages, not just ones on disk\n const languages = config.languages.filter((lang) => lang !== sourceLanguage);\n\n // Sync structure before validation to ensure all files exist and clean orphaned keys\n const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);\n\n if (syncResult.createdFiles.length > 0) {\n console.log(`Created ${syncResult.createdFiles.length} missing namespace files during sync`);\n }\n\n if (syncResult.cleanedKeys.length > 0) {\n console.log(`Cleaned ${syncResult.cleanedKeys.length} orphaned keys during sync`);\n }\n\n console.log('=====');\n console.log('Validating translations');\n console.log('=====');\n console.log(`Source language: ${sourceLanguage}`);\n console.log(`Target languages: ${languages.join(', ')}`);\n console.log(`Namespaces: ${sourceNamespaces.join(', ')}`);\n console.log('=====');\n\n // Validate each language\n for (const language of languages) {\n const targetTranslations = readTranslations(translationsPath, language);\n\n // Check each namespace\n for (const namespace of sourceNamespaces) {\n const sourceKeys = sourceTranslations[namespace] || {};\n const targetKeys = targetTranslations[namespace] || {};\n\n // Check for missing or empty translations\n for (const [key, sourceValue] of Object.entries(sourceKeys)) {\n const targetValue = targetKeys[key];\n\n // Missing key in target language\n if (targetValue === undefined) {\n missing.push({\n namespace,\n key,\n language,\n sourceValue\n });\n }\n // Empty value in target language\n else if (typeof targetValue === 'string' && targetValue.trim() === '') {\n empty.push({\n namespace,\n key,\n language,\n sourceValue\n });\n }\n }\n\n // Check for orphaned keys (exist in target but not in source)\n for (const [key, targetValue] of Object.entries(targetKeys)) {\n if (sourceKeys[key] === undefined) {\n orphaned.push({\n namespace,\n key,\n language,\n value: targetValue\n });\n }\n }\n }\n }\n\n const valid = !missing.length && !empty.length && !orphaned.length;\n\n if (valid) {\n console.log('✓ All translations are valid!');\n } else {\n if (missing.length > 0) {\n console.log(`\\n⚠ Found ${missing.length} missing translations:`);\n for (const item of missing.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (missing.length > 10) {\n console.log(` ... and ${missing.length - 10} more`);\n }\n }\n\n if (empty.length > 0) {\n console.log(`\\n⚠ Found ${empty.length} empty translations:`);\n for (const item of empty.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (empty.length > 10) {\n console.log(` ... and ${empty.length - 10} more`);\n }\n }\n\n if (orphaned.length > 0) {\n console.log(`\\n⚠ Found ${orphaned.length} orphaned translations (keys removed from source):`);\n for (const item of orphaned.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (orphaned.length > 10) {\n console.log(` ... and ${orphaned.length - 10} more`);\n }\n }\n }\n\n console.log('=====');\n\n return { valid, missing, empty, orphaned };\n}\n\n/**\n * Get all missing or empty translations for a specific language\n */\nexport function getMissingForLanguage(\n projectRoot: string,\n language: string\n): Array<MissingTranslation & { type: 'missing' | 'empty' }> {\n const result = validateTranslations(projectRoot);\n const items = [\n ...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: 'missing' as const })),\n ...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: 'empty' as const }))\n ];\n\n return items;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { TranslationFile, TranslationFiles } from '../core/types.js';\n\n/**\n * Nested translation structure (allows nested objects)\n */\nexport type NestedTranslationFile = {\n [key: string]: string | NestedTranslationFile;\n};\n\n/**\n * Flatten a nested object into a flat key-value structure using dot notation\n * Example: { home: { title: \"Hello\" } } -> { \"home.title\": \"Hello\" }\n */\nexport function flattenObject(obj: NestedTranslationFile, prefix = ''): TranslationFile {\n const result: TranslationFile = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (typeof value === 'string') {\n result[newKey] = value;\n } else if (typeof value === 'object' && value !== null) {\n Object.assign(result, flattenObject(value, newKey));\n }\n }\n\n return result;\n}\n\n/**\n * Unflatten a flat object with dot notation keys into a nested structure\n * Example: { \"home.title\": \"Hello\" } -> { home: { title: \"Hello\" } }\n */\nexport function unflattenObject(obj: TranslationFile): NestedTranslationFile {\n const result: NestedTranslationFile = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const parts = key.split('.');\n let current: NestedTranslationFile = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (!(part in current) || typeof current[part] === 'string') {\n current[part] = {};\n }\n current = current[part] as NestedTranslationFile;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n/**\n * Check if an object is flat (all values are strings) or nested\n */\nexport function isNestedObject(obj: Record<string, unknown>): boolean {\n return Object.values(obj).some((value) => typeof value === 'object' && value !== null);\n}\n\n/**\n * Read all translation files for a specific language\n * Automatically flattens nested structures into dot notation\n */\nexport function readTranslations(translationsPath: string, language: string): TranslationFiles {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n return {};\n }\n\n const files = fs.readdirSync(langPath).filter((f) => f.endsWith('.json'));\n const translations: TranslationFiles = {};\n\n for (const file of files) {\n const namespace = path.basename(file, '.json');\n const filePath = path.join(langPath, file);\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(content) as NestedTranslationFile;\n\n // Flatten nested structures automatically\n if (isNestedObject(parsed)) {\n translations[namespace] = flattenObject(parsed);\n } else {\n translations[namespace] = parsed as TranslationFile;\n }\n }\n\n return translations;\n}\n\n/**\n * Write translation file for a specific language and namespace\n */\nexport function writeTranslation(\n translationsPath: string,\n language: string,\n namespace: string,\n translations: TranslationFile\n): void {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n fs.mkdirSync(langPath, { recursive: true });\n }\n\n const filePath = path.join(langPath, `${namespace}.json`);\n fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}\\n`, 'utf-8');\n}\n\n/**\n * Get all available languages from the translations directory\n */\nexport function getAvailableLanguages(translationsPath: string): string[] {\n if (!fs.existsSync(translationsPath)) {\n return [];\n }\n\n return fs\n .readdirSync(translationsPath, { withFileTypes: true })\n .filter((dirent) => dirent.isDirectory())\n .map((dirent) => dirent.name);\n}\n\n/**\n * Get all namespaces for a specific language\n */\nexport function getNamespaces(translationsPath: string, language: string): string[] {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n return [];\n }\n\n return fs\n .readdirSync(langPath)\n .filter((f) => f.endsWith('.json'))\n .map((f) => path.basename(f, '.json'));\n}\n\n/**\n * Check if a string contains interpolation variables (e.g., {{variable}})\n */\nexport function hasInterpolation(text: string): boolean {\n return /\\{\\{[^}]+\\}\\}/g.test(text);\n}\n\n/**\n * Extract interpolation variable names from a string\n */\nexport function extractVariables(text: string): string[] {\n const matches = text.match(/\\{\\{([^}]+)\\}\\}/g);\n if (!matches) return [];\n return matches.map((match) => match.replace(/\\{\\{|\\}\\}/g, '').trim());\n}\n\n/**\n * Validate that translated text has the same variables as source text\n */\nexport function validateVariables(sourceText: string, translatedText: string): boolean {\n const sourceVars = extractVariables(sourceText).sort();\n const translatedVars = extractVariables(translatedText).sort();\n\n if (sourceVars.length !== translatedVars.length) {\n return false;\n }\n\n return sourceVars.every((v, i) => v === translatedVars[i]);\n}\n\n/**\n * Sort object keys alphabetically\n */\nexport function sortKeys<T extends Record<string, unknown>>(obj: T): T {\n const sorted = {} as T;\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n sorted[key as keyof T] = obj[key as keyof T];\n }\n return sorted;\n}\n\n/**\n * Ensure translations path exists and has proper structure\n */\nexport function ensureTranslationsStructure(translationsPath: string, languages: string[]): void {\n if (!fs.existsSync(translationsPath)) {\n fs.mkdirSync(translationsPath, { recursive: true });\n }\n\n for (const lang of languages) {\n const langPath = path.join(translationsPath, lang);\n if (!fs.existsSync(langPath)) {\n fs.mkdirSync(langPath, { recursive: true });\n }\n }\n}\n\n/**\n * Create an empty translation structure from a source translation file\n * All values are set to empty strings, preserving the key structure\n */\nexport function createEmptyTranslationStructure(sourceFile: TranslationFile): TranslationFile {\n const result: TranslationFile = {};\n\n for (const key of Object.keys(sourceFile)) {\n result[key] = '';\n }\n\n return result;\n}\n\nexport interface SyncResult {\n createdFolders: string[];\n createdFiles: Array<{ language: string; namespace: string; path: string }>;\n skippedFiles: Array<{ language: string; namespace: string; reason: string }>;\n cleanedKeys: Array<{ language: string; namespace: string; key: string }>;\n removedNamespaces: Array<{ language: string; namespace: string; path: string }>;\n}\n\n/**\n * Synchronize translation structure based on source language\n * - Ensures all configured languages have folders\n * - Ensures all namespaces from source language exist in target languages\n * - Creates files with empty values matching source structure\n * - Removes orphaned namespace files from target languages (namespaces not in source)\n * - Removes orphaned keys from target languages (keys not in source)\n */\nexport function syncTranslationStructure(\n translationsPath: string,\n languages: string[],\n sourceLanguage: string\n): SyncResult {\n const result: SyncResult = {\n createdFolders: [],\n createdFiles: [],\n skippedFiles: [],\n cleanedKeys: [],\n removedNamespaces: []\n };\n\n // 1. Ensure all language folders exist\n ensureTranslationsStructure(translationsPath, languages);\n\n // 2. Get all namespaces from source language\n const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);\n\n if (!sourceNamespaces.length) {\n return result; // No namespaces to sync\n }\n\n // 3. Read source translations once\n const sourceTranslations = readTranslations(translationsPath, sourceLanguage);\n\n // 4. Sync each target language\n const targetLanguages = languages.filter((lang) => lang !== sourceLanguage);\n\n for (const language of targetLanguages) {\n const targetTranslations = readTranslations(translationsPath, language);\n const targetNamespaces = getNamespaces(translationsPath, language);\n\n // 4.1. Remove orphaned namespace files (files that don't exist in source)\n for (const namespace of targetNamespaces) {\n if (!sourceNamespaces.includes(namespace)) {\n const filePath = path.join(translationsPath, language, `${namespace}.json`);\n fs.unlinkSync(filePath);\n result.removedNamespaces.push({\n language,\n namespace,\n path: filePath\n });\n }\n }\n\n // 4.2. Sync each source namespace\n for (const namespace of sourceNamespaces) {\n const filePath = path.join(translationsPath, language, `${namespace}.json`);\n const sourceFile = sourceTranslations[namespace] || {};\n\n // Check if file already exists\n if (fs.existsSync(filePath)) {\n // File exists - check for orphaned keys and remove them\n const targetFile = targetTranslations[namespace] || {};\n let hasOrphanedKeys = false;\n const cleanedFile: TranslationFile = {};\n\n // Only keep keys that exist in source\n for (const key of Object.keys(targetFile)) {\n if (sourceFile[key] !== undefined) {\n cleanedFile[key] = targetFile[key];\n } else {\n // Orphaned key found\n hasOrphanedKeys = true;\n result.cleanedKeys.push({\n language,\n namespace,\n key\n });\n }\n }\n\n // Add missing keys with empty values\n for (const key of Object.keys(sourceFile)) {\n if (cleanedFile[key] === undefined) {\n cleanedFile[key] = '';\n }\n }\n\n // Write the cleaned file if there were orphaned keys\n if (hasOrphanedKeys) {\n writeTranslation(translationsPath, language, namespace, cleanedFile);\n }\n\n result.skippedFiles.push({\n language,\n namespace,\n reason: hasOrphanedKeys ? 'cleaned orphaned keys' : 'already exists'\n });\n continue;\n }\n\n // Create empty structure from source\n const emptyStructure = createEmptyTranslationStructure(sourceFile);\n\n // Write the file\n writeTranslation(translationsPath, language, namespace, emptyStructure);\n\n result.createdFiles.push({\n language,\n namespace,\n path: filePath\n });\n }\n }\n\n return result;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { validateLanguages } from '../core/schema.js';\nimport type { TranslationConfig } from '../core/types.js';\nimport { DEFAULT_CONFIG, DEFAULT_LANGUAGES } from '../core/types.js';\nimport { ensureTranslationsStructure, getAvailableLanguages, syncTranslationStructure } from '../utils/utils.js';\n\n/**\n * Detect existing translation structure in common locations\n */\nexport function detectExistingTranslations(projectRoot: string): {\n path: string | null;\n languages: string[];\n} {\n const possiblePaths = ['public/static/locales', 'public/locales', 'src/locales', 'locales', 'i18n', 'translations'];\n\n for (const possiblePath of possiblePaths) {\n const fullPath = path.join(projectRoot, possiblePath);\n if (fs.existsSync(fullPath)) {\n const languages = getAvailableLanguages(fullPath);\n if (languages.length > 0) {\n return { path: possiblePath, languages };\n }\n }\n }\n\n return { path: null, languages: [] };\n}\n\n/**\n * Initialize translation structure for a project\n */\nexport function initTranslations(projectRoot: string, config: TranslationConfig = {}): void {\n console.log('=====');\n console.log('Initializing translation structure');\n console.log('=====');\n\n // Detect existing translations\n const existing = detectExistingTranslations(projectRoot);\n\n let finalConfig = { ...DEFAULT_CONFIG, ...config };\n\n // Use detected path and languages if found\n if (existing.path && existing.languages.length > 0) {\n console.log(`✓ Detected existing translations at: ${existing.path}`);\n console.log(`✓ Found languages: ${existing.languages.join(', ')}`);\n\n // Validate detected languages\n const validation = validateLanguages(existing.languages);\n if (!validation.valid) {\n console.log(`⚠️ Warning: Invalid language codes found: ${validation.invalid.join(', ')}`);\n console.log('These languages will be skipped.');\n }\n\n const validLanguages = existing.languages.filter((lang) => !validation.invalid.includes(lang));\n\n finalConfig = {\n ...finalConfig,\n translationsPath: existing.path,\n languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages\n };\n }\n\n const translationsPath = path.join(projectRoot, finalConfig.translationsPath);\n const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];\n\n console.log(`Project root: ${projectRoot}`);\n console.log(`Translations path: ${translationsPath}`);\n console.log(`Languages: ${languages.join(', ')}`);\n console.log('=====');\n\n // Validate languages\n const validation = validateLanguages(languages);\n if (!validation.valid) {\n throw new Error(`Invalid language codes: ${validation.invalid.join(', ')}`);\n }\n\n // Create directory structure\n ensureTranslationsStructure(translationsPath, languages);\n\n // Create sample translation files for English (source language)\n const sourceLanguage = finalConfig.sourceLanguage;\n const sourcePath = path.join(translationsPath, sourceLanguage);\n\n // Create a sample common.json if it doesn't exist\n const commonPath = path.join(sourcePath, 'common.json');\n if (!fs.existsSync(commonPath)) {\n const sampleTranslations = {\n LOADING: 'Loading',\n SAVE: 'Save',\n CANCEL: 'Cancel',\n SUBMIT: 'Submit',\n ERROR: 'Error',\n SUCCESS: 'Success'\n };\n\n fs.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}\\n`, 'utf-8');\n console.log(`Created sample file: ${commonPath}`);\n }\n\n // Sync all namespaces from source to target languages\n console.log('\\nSynchronizing translation structure...');\n const syncResult = syncTranslationStructure(translationsPath, languages, sourceLanguage);\n\n if (syncResult.createdFiles.length > 0) {\n console.log(`Created ${syncResult.createdFiles.length} namespace files in target languages`);\n const languageGroups = new Set(syncResult.createdFiles.map((f) => f.language));\n for (const lang of languageGroups) {\n const langFiles = syncResult.createdFiles.filter((f) => f.language === lang);\n console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(', ')}`);\n }\n }\n\n // Create config file with schema reference\n const configPath = path.join(projectRoot, '.translationsrc.json');\n if (!fs.existsSync(configPath)) {\n const configContent = {\n $schema: './node_modules/poly-lexis/dist/translations/core/translations-config.schema.json',\n translationsPath: finalConfig.translationsPath,\n languages,\n sourceLanguage,\n typesOutputPath: finalConfig.typesOutputPath,\n provider: finalConfig.provider\n };\n\n fs.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}\\n`, 'utf-8');\n console.log(`Created config file: ${configPath}`);\n }\n\n console.log('=====');\n console.log('Translation structure initialized successfully!');\n console.log('=====');\n}\n\n/**\n * Load translation configuration from .translationsrc.json\n */\nexport function loadConfig(projectRoot: string): Required<TranslationConfig> {\n const configPath = path.join(projectRoot, '.translationsrc.json');\n\n if (!fs.existsSync(configPath)) {\n // Try to detect existing translations\n const existing = detectExistingTranslations(projectRoot);\n if (existing.path && existing.languages.length > 0) {\n console.log(`ℹ️ No config found, but detected translations at ${existing.path}`);\n return {\n ...DEFAULT_CONFIG,\n translationsPath: existing.path,\n languages: existing.languages\n };\n }\n return DEFAULT_CONFIG;\n }\n\n const configContent = fs.readFileSync(configPath, 'utf-8');\n const config = JSON.parse(configContent) as TranslationConfig;\n\n // Validate languages in config\n if (config.languages) {\n const validation = validateLanguages(config.languages);\n if (!validation.valid) {\n console.warn(`⚠️ Warning: Invalid language codes in config: ${validation.invalid.join(', ')}`);\n console.warn('Please update .translationsrc.json with valid language codes.');\n }\n }\n\n return { ...DEFAULT_CONFIG, ...config };\n}\n","/**\n * Translation provider types\n */\nexport const TRANSLATION_PROVIDERS = ['deepl', 'google'] as const;\nexport type TranslationProviderType = (typeof TRANSLATION_PROVIDERS)[number];\n\n/**\n * DeepL supported target languages\n * Based on DeepL API v2 documentation\n */\nexport const DEEPL_LANGUAGES = [\n 'ar', // Arabic\n 'bg', // Bulgarian\n 'cs', // Czech\n 'da', // Danish\n 'de', // German\n 'el', // Greek\n 'en', // English (unspecified)\n 'en_gb', // English (British)\n 'en_us', // English (American)\n 'es', // Spanish\n 'es_419', // Spanish (Latin American)\n 'et', // Estonian\n 'fi', // Finnish\n 'fr', // French\n 'he', // Hebrew (next-gen models only)\n 'hu', // Hungarian\n 'id', // Indonesian\n 'it', // Italian\n 'ja', // Japanese\n 'ko', // Korean\n 'lt', // Lithuanian\n 'lv', // Latvian\n 'nb', // Norwegian Bokmål\n 'nl', // Dutch\n 'pl', // Polish\n 'pt', // Portuguese (unspecified)\n 'pt_br', // Portuguese (Brazilian)\n 'pt_pt', // Portuguese (excluding Brazilian)\n 'ro', // Romanian\n 'ru', // Russian\n 'sk', // Slovak\n 'sl', // Slovenian\n 'sv', // Swedish\n 'th', // Thai (next-gen models only)\n 'tr', // Turkish\n 'uk', // Ukrainian\n 'vi', // Vietnamese (next-gen models only)\n 'zh', // Chinese (unspecified)\n 'zh_hans', // Chinese (simplified)\n 'zh_hant' // Chinese (traditional)\n] as const;\n\n/**\n * Google Translate supported languages\n * Based on Google Cloud Translation API v2\n * Reference: https://docs.cloud.google.com/translate/docs/languages\n */\nexport const GOOGLE_LANGUAGES = [\n 'af', // Afrikaans\n 'sq', // Albanian\n 'am', // Amharic\n 'ar', // Arabic\n 'hy', // Armenian\n 'az', // Azerbaijani\n 'eu', // Basque\n 'be', // Belarusian\n 'bn', // Bengali\n 'bs', // Bosnian\n 'bg', // Bulgarian\n 'ca', // Catalan\n 'ceb', // Cebuano\n 'zh', // Chinese (Simplified)\n 'zh_cn', // Chinese (Simplified)\n 'zh_tw', // Chinese (Traditional)\n 'co', // Corsican\n 'hr', // Croatian\n 'cs', // Czech\n 'da', // Danish\n 'nl', // Dutch\n 'en', // English\n 'eo', // Esperanto\n 'et', // Estonian\n 'fi', // Finnish\n 'fr', // French\n 'fy', // Frisian\n 'gl', // Galician\n 'ka', // Georgian\n 'de', // German\n 'el', // Greek\n 'gu', // Gujarati\n 'ht', // Haitian Creole\n 'ha', // Hausa\n 'haw', // Hawaiian\n 'he', // Hebrew\n 'hi', // Hindi\n 'hmn', // Hmong\n 'hu', // Hungarian\n 'is', // Icelandic\n 'ig', // Igbo\n 'id', // Indonesian\n 'ga', // Irish\n 'it', // Italian\n 'ja', // Japanese\n 'jv', // Javanese\n 'kn', // Kannada\n 'kk', // Kazakh\n 'km', // Khmer\n 'rw', // Kinyarwanda\n 'ko', // Korean\n 'ku', // Kurdish\n 'ky', // Kyrgyz\n 'lo', // Lao\n 'la', // Latin\n 'lv', // Latvian\n 'lt', // Lithuanian\n 'lb', // Luxembourgish\n 'mk', // Macedonian\n 'mg', // Malagasy\n 'ms', // Malay\n 'ml', // Malayalam\n 'mt', // Maltese\n 'mi', // Maori\n 'mr', // Marathi\n 'mn', // Mongolian\n 'my', // Myanmar (Burmese)\n 'ne', // Nepali\n 'no', // Norwegian\n 'ny', // Nyanja (Chichewa)\n 'or', // Odia (Oriya)\n 'ps', // Pashto\n 'fa', // Persian\n 'pl', // Polish\n 'pt', // Portuguese\n 'pt_br', // Portuguese (Brazil)\n 'pa', // Punjabi\n 'ro', // Romanian\n 'ru', // Russian\n 'sm', // Samoan\n 'gd', // Scots Gaelic\n 'sr', // Serbian\n 'st', // Sesotho\n 'sn', // Shona\n 'sd', // Sindhi\n 'si', // Sinhala (Sinhalese)\n 'sk', // Slovak\n 'sl', // Slovenian\n 'so', // Somali\n 'es', // Spanish\n 'su', // Sundanese\n 'sw', // Swahili\n 'sv', // Swedish\n 'tl', // Tagalog (Filipino)\n 'tg', // Tajik\n 'ta', // Tamil\n 'tt', // Tatar\n 'te', // Telugu\n 'th', // Thai\n 'tr', // Turkish\n 'tk', // Turkmen\n 'uk', // Ukrainian\n 'ur', // Urdu\n 'ug', // Uyghur\n 'uz', // Uzbek\n 'vi', // Vietnamese\n 'cy', // Welsh\n 'xh', // Xhosa\n 'yi', // Yiddish\n 'yo', // Yoruba\n 'zu' // Zulu\n] as const;\n\n/**\n * All supported language codes (union of DeepL and Google Translate)\n */\nexport const SUPPORTED_LANGUAGES = Array.from(\n new Set([...DEEPL_LANGUAGES, ...GOOGLE_LANGUAGES])\n).sort() as readonly string[];\n\nexport type DeepLLanguage = (typeof DEEPL_LANGUAGES)[number];\nexport type GoogleLanguage = (typeof GOOGLE_LANGUAGES)[number];\nexport type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];\n\n/**\n * JSON Schema for .translationsrc.json\n */\nexport const TRANSLATION_CONFIG_SCHEMA = {\n title: 'Translation Configuration',\n description: 'Configuration for the translation management system',\n type: 'object',\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema reference'\n },\n translationsPath: {\n type: 'string',\n description: 'Path to the translations directory relative to project root',\n default: 'public/static/locales',\n examples: ['public/static/locales', 'src/locales', 'locales']\n },\n languages: {\n type: 'array',\n description: 'List of language codes to support',\n items: {\n type: 'string',\n enum: SUPPORTED_LANGUAGES\n },\n minItems: 1,\n uniqueItems: true,\n default: ['en']\n },\n sourceLanguage: {\n type: 'string',\n description: 'Source language for translations (usually \"en\")',\n enum: SUPPORTED_LANGUAGES,\n default: 'en'\n },\n typesOutputPath: {\n type: 'string',\n description: 'Path to output TypeScript types file',\n default: 'src/types/i18nTypes.ts',\n examples: ['src/types/i18nTypes.ts', 'src/types/translations.ts']\n },\n provider: {\n type: 'string',\n description: 'Translation provider to use (deepl or google)',\n enum: TRANSLATION_PROVIDERS,\n default: 'deepl'\n },\n useFallbackLanguages: {\n type: 'boolean',\n description: 'Enable automatic language fallback for unsupported regional variants (e.g., de_at -> de)',\n default: false\n },\n searchPaths: {\n type: 'array',\n description: 'Directories to search for translation key usage',\n items: {\n type: 'string'\n },\n default: ['src', 'app', 'pages', 'components']\n },\n searchExtensions: {\n type: 'array',\n description: 'File extensions to search for key usage',\n items: {\n type: 'string'\n },\n default: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte']\n },\n protectedTerms: {\n type: 'array',\n description: 'List of terms that should never be translated (e.g. brand names, technical terms)',\n items: {\n type: 'string'\n },\n default: []\n }\n },\n required: ['translationsPath', 'languages', 'sourceLanguage'],\n additionalProperties: false\n};\n\n/**\n * Validate if a language code is supported\n */\nexport function isValidLanguage(lang: string): lang is SupportedLanguage {\n return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage);\n}\n\n/**\n * Validate if a language is supported by DeepL\n */\nexport function isValidDeepLLanguage(lang: string): lang is DeepLLanguage {\n return DEEPL_LANGUAGES.includes(lang as DeepLLanguage);\n}\n\n/**\n * Validate if a language is supported by Google Translate\n */\nexport function isValidGoogleLanguage(lang: string): lang is GoogleLanguage {\n return GOOGLE_LANGUAGES.includes(lang as GoogleLanguage);\n}\n\n/**\n * Validate if a language is supported by a specific provider\n */\nexport function isValidLanguageForProvider(lang: string, provider: TranslationProviderType): boolean {\n switch (provider) {\n case 'deepl':\n return isValidDeepLLanguage(lang);\n case 'google':\n return isValidGoogleLanguage(lang);\n default:\n return false;\n }\n}\n\n/**\n * Validate languages array\n */\nexport function validateLanguages(languages: string[]): {\n valid: boolean;\n invalid: string[];\n} {\n const invalid = languages.filter((lang) => !isValidLanguage(lang));\n return {\n valid: !invalid.length,\n invalid\n };\n}\n\n/**\n * Validate languages array for a specific provider\n */\nexport function validateLanguagesForProvider(\n languages: string[],\n provider: TranslationProviderType\n): {\n valid: boolean;\n invalid: string[];\n} {\n const invalid = languages.filter((lang) => !isValidLanguageForProvider(lang, provider));\n return {\n valid: !invalid.length,\n invalid\n };\n}\n\n/**\n * Get supported languages for a specific provider\n */\nexport function getSupportedLanguages(provider: TranslationProviderType): readonly string[] {\n switch (provider) {\n case 'deepl':\n return DEEPL_LANGUAGES;\n case 'google':\n return GOOGLE_LANGUAGES;\n default:\n return [];\n }\n}\n\n/**\n * Re-export language fallback utilities for convenience\n */\nexport {\n getFallbackMappings,\n type LanguageFallbackResult,\n logLanguageFallback,\n resolveLanguageWithFallback\n} from '../utils/language-fallback.js';\n","import type { TranslationProviderType } from './schema.js';\n\nexport interface TranslationConfig {\n /** Path to the translations directory (default: public/static/locales) */\n translationsPath?: string;\n /** Languages to support (default: ['en']) */\n languages?: string[];\n /** Source language for translations (default: 'en') */\n sourceLanguage?: string;\n /** Path to output i18n types (default: src/types/i18nTypes.ts) */\n typesOutputPath?: string;\n /** Translation provider to use (default: 'deepl') */\n provider?: TranslationProviderType;\n /** Enable automatic language fallback for unsupported regional variants (default: true) */\n useFallbackLanguages?: boolean;\n /** Directories to search for translation key usage (default: ['src', 'app', 'pages', 'components']) */\n searchPaths?: string[];\n /** File extensions to search for key usage (default: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte']) */\n searchExtensions?: string[];\n /** Words or phrases that should never be translated (e.g. brand names, product names) */\n protectedTerms?: string[];\n}\n\nexport interface TranslationEntry {\n namespace: string;\n key: string;\n value: string;\n}\n\nexport interface TranslationFile {\n [key: string]: string;\n}\n\nexport interface TranslationFiles {\n [namespace: string]: TranslationFile;\n}\n\nexport interface MissingTranslation {\n namespace: string;\n key: string;\n language: string;\n sourceValue: string;\n}\n\nexport interface OrphanedTranslation {\n namespace: string;\n key: string;\n language: string;\n value: string;\n}\n\nexport interface UnusedTranslation {\n namespace: string;\n key: string;\n usageType: 'unused' | 'partial';\n partialMatches?: string[];\n}\n\nexport interface ValidationResult {\n valid: boolean;\n missing: MissingTranslation[];\n empty: MissingTranslation[];\n orphaned: OrphanedTranslation[];\n}\n\nexport interface UnusedKeysResult {\n unused: UnusedTranslation[];\n totalKeys: number;\n searchedFiles: number;\n}\n\nexport interface DuplicateTranslation {\n namespace: string; // the non-common namespace where the dup lives\n key: string; // the key in that namespace\n commonKey: string; // the matching key in common\n value: string; // the shared value\n}\n\nexport interface DuplicateKeysResult {\n duplicates: DuplicateTranslation[];\n totalKeysChecked: number;\n}\n\nexport const DEFAULT_CONFIG: Required<TranslationConfig> = {\n translationsPath: 'public/static/locales',\n languages: ['en'],\n sourceLanguage: 'en',\n typesOutputPath: 'src/types/i18nTypes.ts',\n provider: 'deepl',\n useFallbackLanguages: true,\n searchPaths: ['src', 'app', 'pages', 'components'],\n searchExtensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte'],\n protectedTerms: []\n};\n\nexport const DEFAULT_LANGUAGES = ['en', 'fr', 'it', 'pl', 'es', 'pt', 'de', 'nl', 'sv', 'hu', 'cs', 'ja'] as const;\n","#!/usr/bin/env node\nimport { validateTranslations } from '../translations/cli/validate.js';\n\n/**\n * Script to verify that all translations have no missing keys\n * Exits with error code 1 if there are any missing or empty translations\n * This is useful for CI/CD pipelines to ensure translation completeness\n */\nfunction verifyTranslations(): void {\n const projectRoot = process.cwd();\n\n try {\n const result = validateTranslations(projectRoot);\n\n if (!result.valid) {\n console.error('\\n❌ Translation verification failed!');\n console.error(\n `Found ${result.missing.length} missing translations, ${result.empty.length} empty translations, and ${result.orphaned.length} orphaned translations.`\n );\n console.error('Please fix these issues before proceeding.\\n');\n process.exit(1);\n }\n\n console.log('\\n✅ All translations verified successfully!');\n process.exit(0);\n } catch (error) {\n console.error('\\n❌ Error during translation verification:');\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n}\n\nverifyTranslations();\n"],"mappings":"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","names":["path","fs","path"]}
1
+ {"version":3,"sources":["../../src/translations/cli/validate.ts","../../src/translations/utils/metadata.ts","../../src/translations/utils/utils.ts","../../src/translations/cli/init.ts","../../src/translations/core/schema.ts","../../src/translations/core/types.ts","../../src/scripts/verify-translations.ts"],"sourcesContent":["import * as path from 'node:path';\nimport type { MissingTranslation, OrphanedTranslation, StaleTranslation, ValidationResult } from '../core/types.js';\nimport {\n getSourceHash,\n hashSourceValue,\n metadataExists,\n readMetadata,\n setSourceHash,\n writeMetadata\n} from '../utils/metadata.js';\nimport { getNamespaces, readTranslations, syncTranslationStructure } from '../utils/utils.js';\nimport { loadConfig } from './init.js';\n\n/**\n * Validate all translations against the source language\n * Checks for missing keys, empty values, and orphaned keys (keys removed from source)\n */\nexport function validateTranslations(projectRoot: string = process.cwd()): ValidationResult {\n const config = loadConfig(projectRoot);\n const translationsPath = path.join(projectRoot, config.translationsPath);\n const sourceLanguage = config.sourceLanguage;\n\n const missing: MissingTranslation[] = [];\n const empty: MissingTranslation[] = [];\n const orphaned: OrphanedTranslation[] = [];\n const stale: StaleTranslation[] = [];\n\n // Read source translations\n const sourceTranslations = readTranslations(translationsPath, sourceLanguage);\n const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);\n\n // Read source-hash metadata to detect translations made stale by source changes\n const metadata = readMetadata(translationsPath);\n\n // CRITICAL: Use config languages instead of filesystem languages\n // This ensures we validate ALL configured languages, not just ones on disk\n const languages = config.languages.filter((lang) => lang !== sourceLanguage);\n\n // Sync structure before validation to ensure all files exist and clean orphaned keys\n const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);\n\n if (syncResult.createdFiles.length > 0) {\n console.log(`Created ${syncResult.createdFiles.length} missing namespace files during sync`);\n }\n\n if (syncResult.cleanedKeys.length > 0) {\n console.log(`Cleaned ${syncResult.cleanedKeys.length} orphaned keys during sync`);\n }\n\n console.log('=====');\n console.log('Validating translations');\n console.log('=====');\n console.log(`Source language: ${sourceLanguage}`);\n console.log(`Target languages: ${languages.join(', ')}`);\n console.log(`Namespaces: ${sourceNamespaces.join(', ')}`);\n console.log('=====');\n\n // Validate each language\n for (const language of languages) {\n const targetTranslations = readTranslations(translationsPath, language);\n\n // Check each namespace\n for (const namespace of sourceNamespaces) {\n const sourceKeys = sourceTranslations[namespace] || {};\n const targetKeys = targetTranslations[namespace] || {};\n\n // Check for missing or empty translations\n for (const [key, sourceValue] of Object.entries(sourceKeys)) {\n const targetValue = targetKeys[key];\n\n // Missing key in target language\n if (targetValue === undefined) {\n missing.push({\n namespace,\n key,\n language,\n sourceValue\n });\n }\n // Empty value in target language\n else if (typeof targetValue === 'string' && targetValue.trim() === '') {\n empty.push({\n namespace,\n key,\n language,\n sourceValue\n });\n }\n // Stale translation: source value changed since this translation was written.\n // Only detectable when a source hash was recorded for this key.\n else if (typeof targetValue === 'string') {\n const recordedHash = getSourceHash(metadata, language, namespace, key);\n if (recordedHash !== undefined && recordedHash !== hashSourceValue(sourceValue)) {\n stale.push({\n namespace,\n key,\n language,\n sourceValue,\n currentValue: targetValue\n });\n }\n }\n }\n\n // Check for orphaned keys (exist in target but not in source)\n for (const [key, targetValue] of Object.entries(targetKeys)) {\n if (sourceKeys[key] === undefined) {\n orphaned.push({\n namespace,\n key,\n language,\n value: targetValue\n });\n }\n }\n }\n }\n\n // Stale translations are surfaced as a warning and do not, on their own,\n // make the translation set invalid (the keys are still present and non-empty).\n const valid = !missing.length && !empty.length && !orphaned.length;\n\n if (valid && !stale.length) {\n console.log('✓ All translations are valid!');\n } else {\n if (missing.length > 0) {\n console.log(`\\n⚠ Found ${missing.length} missing translations:`);\n for (const item of missing.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (missing.length > 10) {\n console.log(` ... and ${missing.length - 10} more`);\n }\n }\n\n if (empty.length > 0) {\n console.log(`\\n⚠ Found ${empty.length} empty translations:`);\n for (const item of empty.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (empty.length > 10) {\n console.log(` ... and ${empty.length - 10} more`);\n }\n }\n\n if (orphaned.length > 0) {\n console.log(`\\n⚠ Found ${orphaned.length} orphaned translations (keys removed from source):`);\n for (const item of orphaned.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (orphaned.length > 10) {\n console.log(` ... and ${orphaned.length - 10} more`);\n }\n }\n\n if (stale.length > 0) {\n console.log(\n `\\n⚠ Found ${stale.length} potentially outdated translations (source value changed since translation):`\n );\n for (const item of stale.slice(0, 10)) {\n console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);\n }\n if (stale.length > 10) {\n console.log(` ... and ${stale.length - 10} more`);\n }\n console.log(' Run auto-fill with --retranslate-changed to update them.');\n }\n }\n\n console.log('=====');\n\n return { valid, missing, empty, orphaned, stale };\n}\n\nexport type FillableType = 'missing' | 'empty' | 'stale';\n\nexport interface GetMissingOptions {\n /** Also include translations whose source value has changed (stale) */\n includeStale?: boolean;\n}\n\n/**\n * Get all missing or empty translations for a specific language.\n * When `includeStale` is set, also returns translations whose source value has\n * changed since they were written (so they can be re-translated).\n */\nexport function getMissingForLanguage(\n projectRoot: string,\n language: string,\n options: GetMissingOptions = {}\n): Array<MissingTranslation & { type: FillableType }> {\n const result = validateTranslations(projectRoot);\n const items: Array<MissingTranslation & { type: FillableType }> = [\n ...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: 'missing' as const })),\n ...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: 'empty' as const }))\n ];\n\n if (options.includeStale) {\n items.push(\n ...result.stale\n .filter((s) => s.language === language)\n .map((s) => ({\n namespace: s.namespace,\n key: s.key,\n language: s.language,\n sourceValue: s.sourceValue,\n type: 'stale' as const\n }))\n );\n }\n\n return items;\n}\n\nexport interface BaselineResult {\n /** Number of existing translations newly recorded against their current source value */\n recorded: number;\n /** Whether the metadata sidecar file was created by this call */\n created: boolean;\n}\n\n/**\n * Establish a change-tracking baseline for an existing translation set.\n *\n * For every key that already has a non-empty translation but no recorded source\n * hash, record the hash of its *current* source value. This lets existing\n * codebases (whose translations predate change tracking) start detecting future\n * source changes without re-translating anything.\n *\n * Keys that already have a recorded hash are left untouched — overwriting them\n * would erase the signal that a translation has gone stale. As a result, this\n * treats whatever is currently in the repo as the up-to-date baseline: a\n * translation that is already stale at adoption time will be accepted as current.\n */\nexport function ensureBaselineMetadata(projectRoot: string = process.cwd()): BaselineResult {\n const config = loadConfig(projectRoot);\n const translationsPath = path.join(projectRoot, config.translationsPath);\n const sourceLanguage = config.sourceLanguage;\n\n const sourceTranslations = readTranslations(translationsPath, sourceLanguage);\n const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);\n const languages = config.languages.filter((lang) => lang !== sourceLanguage);\n\n const existedBefore = metadataExists(translationsPath);\n const metadata = readMetadata(translationsPath);\n\n let recorded = 0;\n\n for (const language of languages) {\n const targetTranslations = readTranslations(translationsPath, language);\n\n for (const namespace of sourceNamespaces) {\n const sourceKeys = sourceTranslations[namespace] || {};\n const targetKeys = targetTranslations[namespace] || {};\n\n for (const [key, sourceValue] of Object.entries(sourceKeys)) {\n const targetValue = targetKeys[key];\n const alreadyTranslated = typeof targetValue === 'string' && targetValue.trim() !== '';\n const alreadyTracked = getSourceHash(metadata, language, namespace, key) !== undefined;\n\n if (alreadyTranslated && !alreadyTracked) {\n setSourceHash(metadata, language, namespace, key, sourceValue);\n recorded++;\n }\n }\n }\n }\n\n // Only write when we actually recorded something, so we never create an empty\n // sidecar file for a project that has nothing translated yet.\n if (recorded > 0) {\n writeMetadata(translationsPath, metadata);\n }\n\n return { recorded, created: !existedBefore && recorded > 0 };\n}\n","import { createHash } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * Name of the sidecar file (stored at the root of the translations directory)\n * that records a hash of the source value each translation was generated from.\n * This lets us detect when a source string has changed since its translations\n * were written, so we can flag stale translations and optionally re-translate them.\n */\nexport const METADATA_FILE_NAME = '.translations-meta.json';\n\n/**\n * Per-language map of namespace -> key -> source value hash.\n */\nexport interface TranslationMetadata {\n /** Schema version, for forward compatibility */\n version: number;\n /** language -> namespace -> key -> hash of the source value at translation time */\n languages: {\n [language: string]: {\n [namespace: string]: {\n [key: string]: string;\n };\n };\n };\n}\n\nconst CURRENT_VERSION = 1;\n\n/**\n * Create an empty metadata structure\n */\nexport function emptyMetadata(): TranslationMetadata {\n return { version: CURRENT_VERSION, languages: {} };\n}\n\n/**\n * Compute a stable hash of a source value. Used to detect changes to source\n * strings after their translations have been generated.\n */\nexport function hashSourceValue(value: string): string {\n return createHash('sha256').update(value).digest('hex').slice(0, 16);\n}\n\n/**\n * Absolute path to the metadata sidecar file\n */\nexport function getMetadataPath(translationsPath: string): string {\n return path.join(translationsPath, METADATA_FILE_NAME);\n}\n\n/**\n * Whether a metadata sidecar file exists for this translations directory\n */\nexport function metadataExists(translationsPath: string): boolean {\n return fs.existsSync(getMetadataPath(translationsPath));\n}\n\n/**\n * Read the metadata sidecar file. Returns an empty structure if it does not\n * exist or cannot be parsed.\n */\nexport function readMetadata(translationsPath: string): TranslationMetadata {\n const metadataPath = getMetadataPath(translationsPath);\n\n if (!fs.existsSync(metadataPath)) {\n return emptyMetadata();\n }\n\n try {\n const parsed = JSON.parse(fs.readFileSync(metadataPath, 'utf-8')) as Partial<TranslationMetadata>;\n return {\n version: parsed.version ?? CURRENT_VERSION,\n languages: parsed.languages ?? {}\n };\n } catch {\n // Corrupt metadata should never break translation operations\n return emptyMetadata();\n }\n}\n\n/**\n * Write the metadata sidecar file\n */\nexport function writeMetadata(translationsPath: string, metadata: TranslationMetadata): void {\n if (!fs.existsSync(translationsPath)) {\n fs.mkdirSync(translationsPath, { recursive: true });\n }\n\n const metadataPath = getMetadataPath(translationsPath);\n fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\\n`, 'utf-8');\n}\n\n/**\n * Record (in memory) the source value a translation was generated from.\n * Mutates and returns the provided metadata object so callers can batch many\n * updates before a single {@link writeMetadata} call.\n */\nexport function setSourceHash(\n metadata: TranslationMetadata,\n language: string,\n namespace: string,\n key: string,\n sourceValue: string\n): TranslationMetadata {\n if (!metadata.languages[language]) {\n metadata.languages[language] = {};\n }\n if (!metadata.languages[language][namespace]) {\n metadata.languages[language][namespace] = {};\n }\n metadata.languages[language][namespace][key] = hashSourceValue(sourceValue);\n return metadata;\n}\n\n/**\n * Get the stored source value hash for a translation, or undefined if none was\n * recorded (e.g. for translations written before tracking was enabled).\n */\nexport function getSourceHash(\n metadata: TranslationMetadata,\n language: string,\n namespace: string,\n key: string\n): string | undefined {\n return metadata.languages[language]?.[namespace]?.[key];\n}\n\n/**\n * Remove any recorded hashes that no longer correspond to a key present in the\n * source translations. `sourceKeysByNamespace` maps each source namespace to the\n * set of keys it currently contains. Returns true if anything was pruned.\n */\nexport function pruneMetadata(\n metadata: TranslationMetadata,\n sourceKeysByNamespace: Record<string, Set<string>>\n): boolean {\n let changed = false;\n\n for (const language of Object.keys(metadata.languages)) {\n const namespaces = metadata.languages[language];\n\n for (const namespace of Object.keys(namespaces)) {\n const sourceKeys = sourceKeysByNamespace[namespace];\n\n // Namespace removed from source entirely\n if (!sourceKeys) {\n delete namespaces[namespace];\n changed = true;\n continue;\n }\n\n for (const key of Object.keys(namespaces[namespace])) {\n if (!sourceKeys.has(key)) {\n delete namespaces[namespace][key];\n changed = true;\n }\n }\n\n if (Object.keys(namespaces[namespace]).length === 0) {\n delete namespaces[namespace];\n changed = true;\n }\n }\n\n if (Object.keys(namespaces).length === 0) {\n delete metadata.languages[language];\n changed = true;\n }\n }\n\n return changed;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { TranslationFile, TranslationFiles } from '../core/types.js';\nimport { metadataExists, pruneMetadata, readMetadata, writeMetadata } from './metadata.js';\n\n/**\n * Nested translation structure (allows nested objects)\n */\nexport type NestedTranslationFile = {\n [key: string]: string | NestedTranslationFile;\n};\n\n/**\n * Flatten a nested object into a flat key-value structure using dot notation\n * Example: { home: { title: \"Hello\" } } -> { \"home.title\": \"Hello\" }\n */\nexport function flattenObject(obj: NestedTranslationFile, prefix = ''): TranslationFile {\n const result: TranslationFile = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n\n if (typeof value === 'string') {\n result[newKey] = value;\n } else if (typeof value === 'object' && value !== null) {\n Object.assign(result, flattenObject(value, newKey));\n }\n }\n\n return result;\n}\n\n/**\n * Unflatten a flat object with dot notation keys into a nested structure\n * Example: { \"home.title\": \"Hello\" } -> { home: { title: \"Hello\" } }\n */\nexport function unflattenObject(obj: TranslationFile): NestedTranslationFile {\n const result: NestedTranslationFile = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const parts = key.split('.');\n let current: NestedTranslationFile = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (!(part in current) || typeof current[part] === 'string') {\n current[part] = {};\n }\n current = current[part] as NestedTranslationFile;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n/**\n * Check if an object is flat (all values are strings) or nested\n */\nexport function isNestedObject(obj: Record<string, unknown>): boolean {\n return Object.values(obj).some((value) => typeof value === 'object' && value !== null);\n}\n\n/**\n * Read all translation files for a specific language\n * Automatically flattens nested structures into dot notation\n */\nexport function readTranslations(translationsPath: string, language: string): TranslationFiles {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n return {};\n }\n\n const files = fs.readdirSync(langPath).filter((f) => f.endsWith('.json'));\n const translations: TranslationFiles = {};\n\n for (const file of files) {\n const namespace = path.basename(file, '.json');\n const filePath = path.join(langPath, file);\n const content = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(content) as NestedTranslationFile;\n\n // Flatten nested structures automatically\n if (isNestedObject(parsed)) {\n translations[namespace] = flattenObject(parsed);\n } else {\n translations[namespace] = parsed as TranslationFile;\n }\n }\n\n return translations;\n}\n\n/**\n * Write translation file for a specific language and namespace\n */\nexport function writeTranslation(\n translationsPath: string,\n language: string,\n namespace: string,\n translations: TranslationFile\n): void {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n fs.mkdirSync(langPath, { recursive: true });\n }\n\n const filePath = path.join(langPath, `${namespace}.json`);\n fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}\\n`, 'utf-8');\n}\n\n/**\n * Get all available languages from the translations directory\n */\nexport function getAvailableLanguages(translationsPath: string): string[] {\n if (!fs.existsSync(translationsPath)) {\n return [];\n }\n\n return fs\n .readdirSync(translationsPath, { withFileTypes: true })\n .filter((dirent) => dirent.isDirectory())\n .map((dirent) => dirent.name);\n}\n\n/**\n * Get all namespaces for a specific language\n */\nexport function getNamespaces(translationsPath: string, language: string): string[] {\n const langPath = path.join(translationsPath, language);\n\n if (!fs.existsSync(langPath)) {\n return [];\n }\n\n return fs\n .readdirSync(langPath)\n .filter((f) => f.endsWith('.json'))\n .map((f) => path.basename(f, '.json'));\n}\n\n/**\n * Check if a string contains interpolation variables (e.g., {{variable}})\n */\nexport function hasInterpolation(text: string): boolean {\n return /\\{\\{[^}]+\\}\\}/g.test(text);\n}\n\n/**\n * Extract interpolation variable names from a string\n */\nexport function extractVariables(text: string): string[] {\n const matches = text.match(/\\{\\{([^}]+)\\}\\}/g);\n if (!matches) return [];\n return matches.map((match) => match.replace(/\\{\\{|\\}\\}/g, '').trim());\n}\n\n/**\n * Validate that translated text has the same variables as source text\n */\nexport function validateVariables(sourceText: string, translatedText: string): boolean {\n const sourceVars = extractVariables(sourceText).sort();\n const translatedVars = extractVariables(translatedText).sort();\n\n if (sourceVars.length !== translatedVars.length) {\n return false;\n }\n\n return sourceVars.every((v, i) => v === translatedVars[i]);\n}\n\n/**\n * Sort object keys alphabetically\n */\nexport function sortKeys<T extends Record<string, unknown>>(obj: T): T {\n const sorted = {} as T;\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n sorted[key as keyof T] = obj[key as keyof T];\n }\n return sorted;\n}\n\n/**\n * Ensure translations path exists and has proper structure\n */\nexport function ensureTranslationsStructure(translationsPath: string, languages: string[]): void {\n if (!fs.existsSync(translationsPath)) {\n fs.mkdirSync(translationsPath, { recursive: true });\n }\n\n for (const lang of languages) {\n const langPath = path.join(translationsPath, lang);\n if (!fs.existsSync(langPath)) {\n fs.mkdirSync(langPath, { recursive: true });\n }\n }\n}\n\n/**\n * Create an empty translation structure from a source translation file\n * All values are set to empty strings, preserving the key structure\n */\nexport function createEmptyTranslationStructure(sourceFile: TranslationFile): TranslationFile {\n const result: TranslationFile = {};\n\n for (const key of Object.keys(sourceFile)) {\n result[key] = '';\n }\n\n return result;\n}\n\nexport interface SyncResult {\n createdFolders: string[];\n createdFiles: Array<{ language: string; namespace: string; path: string }>;\n skippedFiles: Array<{ language: string; namespace: string; reason: string }>;\n cleanedKeys: Array<{ language: string; namespace: string; key: string }>;\n removedNamespaces: Array<{ language: string; namespace: string; path: string }>;\n}\n\n/**\n * Synchronize translation structure based on source language\n * - Ensures all configured languages have folders\n * - Ensures all namespaces from source language exist in target languages\n * - Creates files with empty values matching source structure\n * - Removes orphaned namespace files from target languages (namespaces not in source)\n * - Removes orphaned keys from target languages (keys not in source)\n */\nexport function syncTranslationStructure(\n translationsPath: string,\n languages: string[],\n sourceLanguage: string\n): SyncResult {\n const result: SyncResult = {\n createdFolders: [],\n createdFiles: [],\n skippedFiles: [],\n cleanedKeys: [],\n removedNamespaces: []\n };\n\n // 1. Ensure all language folders exist\n ensureTranslationsStructure(translationsPath, languages);\n\n // 2. Get all namespaces from source language\n const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);\n\n if (!sourceNamespaces.length) {\n return result; // No namespaces to sync\n }\n\n // 3. Read source translations once\n const sourceTranslations = readTranslations(translationsPath, sourceLanguage);\n\n // 4. Sync each target language\n const targetLanguages = languages.filter((lang) => lang !== sourceLanguage);\n\n for (const language of targetLanguages) {\n const targetTranslations = readTranslations(translationsPath, language);\n const targetNamespaces = getNamespaces(translationsPath, language);\n\n // 4.1. Remove orphaned namespace files (files that don't exist in source)\n for (const namespace of targetNamespaces) {\n if (!sourceNamespaces.includes(namespace)) {\n const filePath = path.join(translationsPath, language, `${namespace}.json`);\n fs.unlinkSync(filePath);\n result.removedNamespaces.push({\n language,\n namespace,\n path: filePath\n });\n }\n }\n\n // 4.2. Sync each source namespace\n for (const namespace of sourceNamespaces) {\n const filePath = path.join(translationsPath, language, `${namespace}.json`);\n const sourceFile = sourceTranslations[namespace] || {};\n\n // Check if file already exists\n if (fs.existsSync(filePath)) {\n // File exists - check for orphaned keys and remove them\n const targetFile = targetTranslations[namespace] || {};\n let hasOrphanedKeys = false;\n const cleanedFile: TranslationFile = {};\n\n // Only keep keys that exist in source\n for (const key of Object.keys(targetFile)) {\n if (sourceFile[key] !== undefined) {\n cleanedFile[key] = targetFile[key];\n } else {\n // Orphaned key found\n hasOrphanedKeys = true;\n result.cleanedKeys.push({\n language,\n namespace,\n key\n });\n }\n }\n\n // Add missing keys with empty values\n for (const key of Object.keys(sourceFile)) {\n if (cleanedFile[key] === undefined) {\n cleanedFile[key] = '';\n }\n }\n\n // Write the cleaned file if there were orphaned keys\n if (hasOrphanedKeys) {\n writeTranslation(translationsPath, language, namespace, cleanedFile);\n }\n\n result.skippedFiles.push({\n language,\n namespace,\n reason: hasOrphanedKeys ? 'cleaned orphaned keys' : 'already exists'\n });\n continue;\n }\n\n // Create empty structure from source\n const emptyStructure = createEmptyTranslationStructure(sourceFile);\n\n // Write the file\n writeTranslation(translationsPath, language, namespace, emptyStructure);\n\n result.createdFiles.push({\n language,\n namespace,\n path: filePath\n });\n }\n }\n\n // Prune source-hash metadata for keys/namespaces no longer in the source,\n // keeping the sidecar file in sync with the translation files.\n if (metadataExists(translationsPath)) {\n const sourceKeysByNamespace: Record<string, Set<string>> = {};\n for (const namespace of sourceNamespaces) {\n sourceKeysByNamespace[namespace] = new Set(Object.keys(sourceTranslations[namespace] || {}));\n }\n\n const metadata = readMetadata(translationsPath);\n if (pruneMetadata(metadata, sourceKeysByNamespace)) {\n writeMetadata(translationsPath, metadata);\n }\n }\n\n return result;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { validateLanguages } from '../core/schema.js';\nimport type { TranslationConfig } from '../core/types.js';\nimport { DEFAULT_CONFIG, DEFAULT_LANGUAGES } from '../core/types.js';\nimport { ensureTranslationsStructure, getAvailableLanguages, syncTranslationStructure } from '../utils/utils.js';\n\n/**\n * Detect existing translation structure in common locations\n */\nexport function detectExistingTranslations(projectRoot: string): {\n path: string | null;\n languages: string[];\n} {\n const possiblePaths = ['public/static/locales', 'public/locales', 'src/locales', 'locales', 'i18n', 'translations'];\n\n for (const possiblePath of possiblePaths) {\n const fullPath = path.join(projectRoot, possiblePath);\n if (fs.existsSync(fullPath)) {\n const languages = getAvailableLanguages(fullPath);\n if (languages.length > 0) {\n return { path: possiblePath, languages };\n }\n }\n }\n\n return { path: null, languages: [] };\n}\n\n/**\n * Initialize translation structure for a project\n */\nexport function initTranslations(projectRoot: string, config: TranslationConfig = {}): void {\n console.log('=====');\n console.log('Initializing translation structure');\n console.log('=====');\n\n // Detect existing translations\n const existing = detectExistingTranslations(projectRoot);\n\n let finalConfig = { ...DEFAULT_CONFIG, ...config };\n\n // Use detected path and languages if found\n if (existing.path && existing.languages.length > 0) {\n console.log(`✓ Detected existing translations at: ${existing.path}`);\n console.log(`✓ Found languages: ${existing.languages.join(', ')}`);\n\n // Validate detected languages\n const validation = validateLanguages(existing.languages);\n if (!validation.valid) {\n console.log(`⚠️ Warning: Invalid language codes found: ${validation.invalid.join(', ')}`);\n console.log('These languages will be skipped.');\n }\n\n const validLanguages = existing.languages.filter((lang) => !validation.invalid.includes(lang));\n\n finalConfig = {\n ...finalConfig,\n translationsPath: existing.path,\n languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages\n };\n }\n\n const translationsPath = path.join(projectRoot, finalConfig.translationsPath);\n const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];\n\n console.log(`Project root: ${projectRoot}`);\n console.log(`Translations path: ${translationsPath}`);\n console.log(`Languages: ${languages.join(', ')}`);\n console.log('=====');\n\n // Validate languages\n const validation = validateLanguages(languages);\n if (!validation.valid) {\n throw new Error(`Invalid language codes: ${validation.invalid.join(', ')}`);\n }\n\n // Create directory structure\n ensureTranslationsStructure(translationsPath, languages);\n\n // Create sample translation files for English (source language)\n const sourceLanguage = finalConfig.sourceLanguage;\n const sourcePath = path.join(translationsPath, sourceLanguage);\n\n // Create a sample common.json if it doesn't exist\n const commonPath = path.join(sourcePath, 'common.json');\n if (!fs.existsSync(commonPath)) {\n const sampleTranslations = {\n LOADING: 'Loading',\n SAVE: 'Save',\n CANCEL: 'Cancel',\n SUBMIT: 'Submit',\n ERROR: 'Error',\n SUCCESS: 'Success'\n };\n\n fs.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}\\n`, 'utf-8');\n console.log(`Created sample file: ${commonPath}`);\n }\n\n // Sync all namespaces from source to target languages\n console.log('\\nSynchronizing translation structure...');\n const syncResult = syncTranslationStructure(translationsPath, languages, sourceLanguage);\n\n if (syncResult.createdFiles.length > 0) {\n console.log(`Created ${syncResult.createdFiles.length} namespace files in target languages`);\n const languageGroups = new Set(syncResult.createdFiles.map((f) => f.language));\n for (const lang of languageGroups) {\n const langFiles = syncResult.createdFiles.filter((f) => f.language === lang);\n console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(', ')}`);\n }\n }\n\n // Create config file with schema reference\n const configPath = path.join(projectRoot, '.translationsrc.json');\n if (!fs.existsSync(configPath)) {\n const configContent = {\n $schema: './node_modules/poly-lexis/dist/translations/core/translations-config.schema.json',\n translationsPath: finalConfig.translationsPath,\n languages,\n sourceLanguage,\n typesOutputPath: finalConfig.typesOutputPath,\n provider: finalConfig.provider\n };\n\n fs.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}\\n`, 'utf-8');\n console.log(`Created config file: ${configPath}`);\n }\n\n console.log('=====');\n console.log('Translation structure initialized successfully!');\n console.log('=====');\n}\n\n/**\n * Load translation configuration from .translationsrc.json\n */\nexport function loadConfig(projectRoot: string): Required<TranslationConfig> {\n const configPath = path.join(projectRoot, '.translationsrc.json');\n\n if (!fs.existsSync(configPath)) {\n // Try to detect existing translations\n const existing = detectExistingTranslations(projectRoot);\n if (existing.path && existing.languages.length > 0) {\n console.log(`ℹ️ No config found, but detected translations at ${existing.path}`);\n return {\n ...DEFAULT_CONFIG,\n translationsPath: existing.path,\n languages: existing.languages\n };\n }\n return DEFAULT_CONFIG;\n }\n\n const configContent = fs.readFileSync(configPath, 'utf-8');\n const config = JSON.parse(configContent) as TranslationConfig;\n\n // Validate languages in config\n if (config.languages) {\n const validation = validateLanguages(config.languages);\n if (!validation.valid) {\n console.warn(`⚠️ Warning: Invalid language codes in config: ${validation.invalid.join(', ')}`);\n console.warn('Please update .translationsrc.json with valid language codes.');\n }\n }\n\n return { ...DEFAULT_CONFIG, ...config };\n}\n","/**\n * Translation provider types\n */\nexport const TRANSLATION_PROVIDERS = ['deepl', 'google'] as const;\nexport type TranslationProviderType = (typeof TRANSLATION_PROVIDERS)[number];\n\n/**\n * DeepL supported target languages\n * Based on DeepL API v2 documentation\n */\nexport const DEEPL_LANGUAGES = [\n 'ar', // Arabic\n 'bg', // Bulgarian\n 'cs', // Czech\n 'da', // Danish\n 'de', // German\n 'el', // Greek\n 'en', // English (unspecified)\n 'en_gb', // English (British)\n 'en_us', // English (American)\n 'es', // Spanish\n 'es_419', // Spanish (Latin American)\n 'et', // Estonian\n 'fi', // Finnish\n 'fr', // French\n 'he', // Hebrew (next-gen models only)\n 'hu', // Hungarian\n 'id', // Indonesian\n 'it', // Italian\n 'ja', // Japanese\n 'ko', // Korean\n 'lt', // Lithuanian\n 'lv', // Latvian\n 'nb', // Norwegian Bokmål\n 'nl', // Dutch\n 'pl', // Polish\n 'pt', // Portuguese (unspecified)\n 'pt_br', // Portuguese (Brazilian)\n 'pt_pt', // Portuguese (excluding Brazilian)\n 'ro', // Romanian\n 'ru', // Russian\n 'sk', // Slovak\n 'sl', // Slovenian\n 'sv', // Swedish\n 'th', // Thai (next-gen models only)\n 'tr', // Turkish\n 'uk', // Ukrainian\n 'vi', // Vietnamese (next-gen models only)\n 'zh', // Chinese (unspecified)\n 'zh_hans', // Chinese (simplified)\n 'zh_hant' // Chinese (traditional)\n] as const;\n\n/**\n * Google Translate supported languages\n * Based on Google Cloud Translation API v2\n * Reference: https://docs.cloud.google.com/translate/docs/languages\n */\nexport const GOOGLE_LANGUAGES = [\n 'af', // Afrikaans\n 'sq', // Albanian\n 'am', // Amharic\n 'ar', // Arabic\n 'hy', // Armenian\n 'az', // Azerbaijani\n 'eu', // Basque\n 'be', // Belarusian\n 'bn', // Bengali\n 'bs', // Bosnian\n 'bg', // Bulgarian\n 'ca', // Catalan\n 'ceb', // Cebuano\n 'zh', // Chinese (Simplified)\n 'zh_cn', // Chinese (Simplified)\n 'zh_tw', // Chinese (Traditional)\n 'co', // Corsican\n 'hr', // Croatian\n 'cs', // Czech\n 'da', // Danish\n 'nl', // Dutch\n 'en', // English\n 'eo', // Esperanto\n 'et', // Estonian\n 'fi', // Finnish\n 'fr', // French\n 'fy', // Frisian\n 'gl', // Galician\n 'ka', // Georgian\n 'de', // German\n 'el', // Greek\n 'gu', // Gujarati\n 'ht', // Haitian Creole\n 'ha', // Hausa\n 'haw', // Hawaiian\n 'he', // Hebrew\n 'hi', // Hindi\n 'hmn', // Hmong\n 'hu', // Hungarian\n 'is', // Icelandic\n 'ig', // Igbo\n 'id', // Indonesian\n 'ga', // Irish\n 'it', // Italian\n 'ja', // Japanese\n 'jv', // Javanese\n 'kn', // Kannada\n 'kk', // Kazakh\n 'km', // Khmer\n 'rw', // Kinyarwanda\n 'ko', // Korean\n 'ku', // Kurdish\n 'ky', // Kyrgyz\n 'lo', // Lao\n 'la', // Latin\n 'lv', // Latvian\n 'lt', // Lithuanian\n 'lb', // Luxembourgish\n 'mk', // Macedonian\n 'mg', // Malagasy\n 'ms', // Malay\n 'ml', // Malayalam\n 'mt', // Maltese\n 'mi', // Maori\n 'mr', // Marathi\n 'mn', // Mongolian\n 'my', // Myanmar (Burmese)\n 'ne', // Nepali\n 'no', // Norwegian\n 'ny', // Nyanja (Chichewa)\n 'or', // Odia (Oriya)\n 'ps', // Pashto\n 'fa', // Persian\n 'pl', // Polish\n 'pt', // Portuguese\n 'pt_br', // Portuguese (Brazil)\n 'pa', // Punjabi\n 'ro', // Romanian\n 'ru', // Russian\n 'sm', // Samoan\n 'gd', // Scots Gaelic\n 'sr', // Serbian\n 'st', // Sesotho\n 'sn', // Shona\n 'sd', // Sindhi\n 'si', // Sinhala (Sinhalese)\n 'sk', // Slovak\n 'sl', // Slovenian\n 'so', // Somali\n 'es', // Spanish\n 'su', // Sundanese\n 'sw', // Swahili\n 'sv', // Swedish\n 'tl', // Tagalog (Filipino)\n 'tg', // Tajik\n 'ta', // Tamil\n 'tt', // Tatar\n 'te', // Telugu\n 'th', // Thai\n 'tr', // Turkish\n 'tk', // Turkmen\n 'uk', // Ukrainian\n 'ur', // Urdu\n 'ug', // Uyghur\n 'uz', // Uzbek\n 'vi', // Vietnamese\n 'cy', // Welsh\n 'xh', // Xhosa\n 'yi', // Yiddish\n 'yo', // Yoruba\n 'zu' // Zulu\n] as const;\n\n/**\n * All supported language codes (union of DeepL and Google Translate)\n */\nexport const SUPPORTED_LANGUAGES = Array.from(\n new Set([...DEEPL_LANGUAGES, ...GOOGLE_LANGUAGES])\n).sort() as readonly string[];\n\nexport type DeepLLanguage = (typeof DEEPL_LANGUAGES)[number];\nexport type GoogleLanguage = (typeof GOOGLE_LANGUAGES)[number];\nexport type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];\n\n/**\n * JSON Schema for .translationsrc.json\n */\nexport const TRANSLATION_CONFIG_SCHEMA = {\n title: 'Translation Configuration',\n description: 'Configuration for the translation management system',\n type: 'object',\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema reference'\n },\n translationsPath: {\n type: 'string',\n description: 'Path to the translations directory relative to project root',\n default: 'public/static/locales',\n examples: ['public/static/locales', 'src/locales', 'locales']\n },\n languages: {\n type: 'array',\n description: 'List of language codes to support',\n items: {\n type: 'string',\n enum: SUPPORTED_LANGUAGES\n },\n minItems: 1,\n uniqueItems: true,\n default: ['en']\n },\n sourceLanguage: {\n type: 'string',\n description: 'Source language for translations (usually \"en\")',\n enum: SUPPORTED_LANGUAGES,\n default: 'en'\n },\n typesOutputPath: {\n type: 'string',\n description: 'Path to output TypeScript types file',\n default: 'src/types/i18nTypes.ts',\n examples: ['src/types/i18nTypes.ts', 'src/types/translations.ts']\n },\n provider: {\n type: 'string',\n description: 'Translation provider to use (deepl or google)',\n enum: TRANSLATION_PROVIDERS,\n default: 'deepl'\n },\n useFallbackLanguages: {\n type: 'boolean',\n description: 'Enable automatic language fallback for unsupported regional variants (e.g., de_at -> de)',\n default: false\n },\n searchPaths: {\n type: 'array',\n description: 'Directories to search for translation key usage',\n items: {\n type: 'string'\n },\n default: ['src', 'app', 'pages', 'components']\n },\n searchExtensions: {\n type: 'array',\n description: 'File extensions to search for key usage',\n items: {\n type: 'string'\n },\n default: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte']\n },\n protectedTerms: {\n type: 'array',\n description: 'List of terms that should never be translated (e.g. brand names, technical terms)',\n items: {\n type: 'string'\n },\n default: []\n }\n },\n required: ['translationsPath', 'languages', 'sourceLanguage'],\n additionalProperties: false\n};\n\n/**\n * Validate if a language code is supported\n */\nexport function isValidLanguage(lang: string): lang is SupportedLanguage {\n return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage);\n}\n\n/**\n * Validate if a language is supported by DeepL\n */\nexport function isValidDeepLLanguage(lang: string): lang is DeepLLanguage {\n return DEEPL_LANGUAGES.includes(lang as DeepLLanguage);\n}\n\n/**\n * Validate if a language is supported by Google Translate\n */\nexport function isValidGoogleLanguage(lang: string): lang is GoogleLanguage {\n return GOOGLE_LANGUAGES.includes(lang as GoogleLanguage);\n}\n\n/**\n * Validate if a language is supported by a specific provider\n */\nexport function isValidLanguageForProvider(lang: string, provider: TranslationProviderType): boolean {\n switch (provider) {\n case 'deepl':\n return isValidDeepLLanguage(lang);\n case 'google':\n return isValidGoogleLanguage(lang);\n default:\n return false;\n }\n}\n\n/**\n * Validate languages array\n */\nexport function validateLanguages(languages: string[]): {\n valid: boolean;\n invalid: string[];\n} {\n const invalid = languages.filter((lang) => !isValidLanguage(lang));\n return {\n valid: !invalid.length,\n invalid\n };\n}\n\n/**\n * Validate languages array for a specific provider\n */\nexport function validateLanguagesForProvider(\n languages: string[],\n provider: TranslationProviderType\n): {\n valid: boolean;\n invalid: string[];\n} {\n const invalid = languages.filter((lang) => !isValidLanguageForProvider(lang, provider));\n return {\n valid: !invalid.length,\n invalid\n };\n}\n\n/**\n * Get supported languages for a specific provider\n */\nexport function getSupportedLanguages(provider: TranslationProviderType): readonly string[] {\n switch (provider) {\n case 'deepl':\n return DEEPL_LANGUAGES;\n case 'google':\n return GOOGLE_LANGUAGES;\n default:\n return [];\n }\n}\n\n/**\n * Re-export language fallback utilities for convenience\n */\nexport {\n getFallbackMappings,\n type LanguageFallbackResult,\n logLanguageFallback,\n resolveLanguageWithFallback\n} from '../utils/language-fallback.js';\n","import type { TranslationProviderType } from './schema.js';\n\nexport interface TranslationConfig {\n /** Path to the translations directory (default: public/static/locales) */\n translationsPath?: string;\n /** Languages to support (default: ['en']) */\n languages?: string[];\n /** Source language for translations (default: 'en') */\n sourceLanguage?: string;\n /** Path to output i18n types (default: src/types/i18nTypes.ts) */\n typesOutputPath?: string;\n /** Translation provider to use (default: 'deepl') */\n provider?: TranslationProviderType;\n /** Enable automatic language fallback for unsupported regional variants (default: true) */\n useFallbackLanguages?: boolean;\n /** Directories to search for translation key usage (default: ['src', 'app', 'pages', 'components']) */\n searchPaths?: string[];\n /** File extensions to search for key usage (default: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte']) */\n searchExtensions?: string[];\n /** Words or phrases that should never be translated (e.g. brand names, product names) */\n protectedTerms?: string[];\n}\n\nexport interface TranslationEntry {\n namespace: string;\n key: string;\n value: string;\n}\n\nexport interface TranslationFile {\n [key: string]: string;\n}\n\nexport interface TranslationFiles {\n [namespace: string]: TranslationFile;\n}\n\nexport interface MissingTranslation {\n namespace: string;\n key: string;\n language: string;\n sourceValue: string;\n}\n\nexport interface OrphanedTranslation {\n namespace: string;\n key: string;\n language: string;\n value: string;\n}\n\nexport interface StaleTranslation {\n namespace: string;\n key: string;\n language: string;\n /** Current source value (the translation should be regenerated from this) */\n sourceValue: string;\n /** The existing, potentially outdated, translated value */\n currentValue: string;\n}\n\nexport interface UnusedTranslation {\n namespace: string;\n key: string;\n usageType: 'unused' | 'partial';\n partialMatches?: string[];\n}\n\nexport interface ValidationResult {\n valid: boolean;\n missing: MissingTranslation[];\n empty: MissingTranslation[];\n orphaned: OrphanedTranslation[];\n /**\n * Translations whose source value has changed since they were generated.\n * Only detectable for translations written with source-hash tracking enabled.\n * Reported as a warning; does not affect `valid`.\n */\n stale: StaleTranslation[];\n}\n\nexport interface UnusedKeysResult {\n unused: UnusedTranslation[];\n totalKeys: number;\n searchedFiles: number;\n}\n\nexport interface DuplicateTranslation {\n namespace: string; // the non-common namespace where the dup lives\n key: string; // the key in that namespace\n commonKey: string; // the matching key in common\n value: string; // the shared value\n}\n\nexport interface DuplicateKeysResult {\n duplicates: DuplicateTranslation[];\n totalKeysChecked: number;\n}\n\nexport const DEFAULT_CONFIG: Required<TranslationConfig> = {\n translationsPath: 'public/static/locales',\n languages: ['en'],\n sourceLanguage: 'en',\n typesOutputPath: 'src/types/i18nTypes.ts',\n provider: 'deepl',\n useFallbackLanguages: true,\n searchPaths: ['src', 'app', 'pages', 'components'],\n searchExtensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte'],\n protectedTerms: []\n};\n\nexport const DEFAULT_LANGUAGES = ['en', 'fr', 'it', 'pl', 'es', 'pt', 'de', 'nl', 'sv', 'hu', 'cs', 'ja'] as const;\n","#!/usr/bin/env node\nimport { validateTranslations } from '../translations/cli/validate.js';\n\n/**\n * Script to verify that all translations have no missing keys\n * Exits with error code 1 if there are any missing or empty translations\n * This is useful for CI/CD pipelines to ensure translation completeness\n */\nfunction verifyTranslations(): void {\n const projectRoot = process.cwd();\n\n try {\n const result = validateTranslations(projectRoot);\n\n if (!result.valid) {\n console.error('\\n❌ Translation verification failed!');\n console.error(\n `Found ${result.missing.length} missing translations, ${result.empty.length} empty translations, and ${result.orphaned.length} orphaned translations.`\n );\n console.error('Please fix these issues before proceeding.\\n');\n process.exit(1);\n }\n\n console.log('\\n✅ All translations verified successfully!');\n process.exit(0);\n } catch (error) {\n console.error('\\n❌ Error during translation verification:');\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n}\n\nverifyTranslations();\n"],"mappings":"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ACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAOO,IAAM,mBAAmB;AAAA,EAC9B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAA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","names":["path","fs","path","fs","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poly-lexis",
3
- "version": "0.9.3",
3
+ "version": "0.11.0",
4
4
  "description": "A powerful CLI and library for managing i18n translations with validation, auto-translation, and TypeScript type generation",
5
5
  "private": false,
6
6
  "type": "module",