poly-lexis 0.9.2 → 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.
@@ -378,6 +378,14 @@ var TRANSLATION_CONFIG_SCHEMA = {
378
378
  type: "string"
379
379
  },
380
380
  default: [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"]
381
+ },
382
+ protectedTerms: {
383
+ type: "array",
384
+ description: "List of terms that should never be translated (e.g. brand names, technical terms)",
385
+ items: {
386
+ type: "string"
387
+ },
388
+ default: []
381
389
  }
382
390
  },
383
391
  required: ["translationsPath", "languages", "sourceLanguage"],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/scripts/generate-schema.ts","../../src/translations/core/schema.ts"],"sourcesContent":["/**\n * Generate JSON schema file from TypeScript schema definition\n * This ensures the JSON schema stays in sync with the TypeScript source\n */\nimport { writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { TRANSLATION_CONFIG_SCHEMA } from '../translations/core/schema.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Write to src directory, not dist\n// When compiled to dist/scripts/, we need to go up 2 levels to get to project root\nconst projectRoot = join(__dirname, '../..');\nconst SCHEMA_OUTPUT_PATH = join(projectRoot, 'src/translations/core/translations-config.schema.json');\n\nconst schemaWithMeta = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://raw.githubusercontent.com/designedhead/lexis/main/src/translations/core/translations-config.schema.json',\n ...TRANSLATION_CONFIG_SCHEMA\n};\n\nwriteFileSync(SCHEMA_OUTPUT_PATH, `${JSON.stringify(schemaWithMeta, null, 2)}\\n`);\n\nconsole.log('✅ Generated translations-config.schema.json');\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 },\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"],"mappings":";AAIA,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;;;ACHvB,IAAM,wBAAwB,CAAC,SAAS,QAAQ;AAOhD,IAAM,kBAAkB;AAAA,EAC7B;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;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;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;AACF;AAKO,IAAM,sBAAsB,MAAM;AAAA,EACvC,oBAAI,IAAI,CAAC,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AACnD,EAAE,KAAK;AASA,IAAM,4BAA4B;AAAA,EACvC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,YAAY;AAAA,IACV,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU,CAAC,yBAAyB,eAAe,SAAS;AAAA,IAC9D;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,MACA,UAAU;AAAA,MACV,aAAa;AAAA,MACb,SAAS,CAAC,IAAI;AAAA,IAChB;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU,CAAC,0BAA0B,2BAA2B;AAAA,IAClE;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,sBAAsB;AAAA,MACpB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,SAAS,CAAC,OAAO,OAAO,SAAS,YAAY;AAAA,IAC/C;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,SAAS,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,SAAS;AAAA,IAC3D;AAAA,EACF;AAAA,EACA,UAAU,CAAC,oBAAoB,aAAa,gBAAgB;AAAA,EAC5D,sBAAsB;AACxB;;;ADrPA,IAAMA,cAAa,cAAc,YAAY,GAAG;AAChD,IAAMC,aAAY,QAAQD,WAAU;AAIpC,IAAM,cAAc,KAAKC,YAAW,OAAO;AAC3C,IAAM,qBAAqB,KAAK,aAAa,uDAAuD;AAEpG,IAAM,iBAAiB;AAAA,EACrB,SAAS;AAAA,EACT,KAAK;AAAA,EACL,GAAG;AACL;AAEA,cAAc,oBAAoB,GAAG,KAAK,UAAU,gBAAgB,MAAM,CAAC,CAAC;AAAA,CAAI;AAEhF,QAAQ,IAAI,kDAA6C;","names":["__filename","__dirname"]}
1
+ {"version":3,"sources":["../../src/scripts/generate-schema.ts","../../src/translations/core/schema.ts"],"sourcesContent":["/**\n * Generate JSON schema file from TypeScript schema definition\n * This ensures the JSON schema stays in sync with the TypeScript source\n */\nimport { writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { TRANSLATION_CONFIG_SCHEMA } from '../translations/core/schema.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Write to src directory, not dist\n// When compiled to dist/scripts/, we need to go up 2 levels to get to project root\nconst projectRoot = join(__dirname, '../..');\nconst SCHEMA_OUTPUT_PATH = join(projectRoot, 'src/translations/core/translations-config.schema.json');\n\nconst schemaWithMeta = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://raw.githubusercontent.com/designedhead/lexis/main/src/translations/core/translations-config.schema.json',\n ...TRANSLATION_CONFIG_SCHEMA\n};\n\nwriteFileSync(SCHEMA_OUTPUT_PATH, `${JSON.stringify(schemaWithMeta, null, 2)}\\n`);\n\nconsole.log('✅ Generated translations-config.schema.json');\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"],"mappings":";AAIA,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;;;ACHvB,IAAM,wBAAwB,CAAC,SAAS,QAAQ;AAOhD,IAAM,kBAAkB;AAAA,EAC7B;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;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;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;AACF;AAKO,IAAM,sBAAsB,MAAM;AAAA,EACvC,oBAAI,IAAI,CAAC,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AACnD,EAAE,KAAK;AASA,IAAM,4BAA4B;AAAA,EACvC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,YAAY;AAAA,IACV,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU,CAAC,yBAAyB,eAAe,SAAS;AAAA,IAC9D;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,MACA,UAAU;AAAA,MACV,aAAa;AAAA,MACb,SAAS,CAAC,IAAI;AAAA,IAChB;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU,CAAC,0BAA0B,2BAA2B;AAAA,IAClE;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,sBAAsB;AAAA,MACpB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,SAAS,CAAC,OAAO,OAAO,SAAS,YAAY;AAAA,IAC/C;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,SAAS,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,SAAS;AAAA,IAC3D;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,MACA,SAAS,CAAC;AAAA,IACZ;AAAA,EACF;AAAA,EACA,UAAU,CAAC,oBAAoB,aAAa,gBAAgB;AAAA,EAC5D,sBAAsB;AACxB;;;AD7PA,IAAMA,cAAa,cAAc,YAAY,GAAG;AAChD,IAAMC,aAAY,QAAQD,WAAU;AAIpC,IAAM,cAAc,KAAKC,YAAW,OAAO;AAC3C,IAAM,qBAAqB,KAAK,aAAa,uDAAuD;AAEpG,IAAM,iBAAiB;AAAA,EACrB,SAAS;AAAA,EACT,KAAK;AAAA,EACL,GAAG;AACL;AAEA,cAAc,oBAAoB,GAAG,KAAK,UAAU,gBAAgB,MAAM,CAAC,CAAC;AAAA,CAAI;AAEhF,QAAQ,IAAI,kDAA6C;","names":["__filename","__dirname"]}
@@ -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