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.
- package/README.md +33 -0
- package/dist/cli/translations.js +382 -108
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +128 -4
- package/dist/index.js +365 -93
- package/dist/index.js.map +1 -1
- package/dist/scripts/generate-schema.js +8 -0
- package/dist/scripts/generate-schema.js.map +1 -1
- package/dist/scripts/verify-translations.js +147 -37
- package/dist/scripts/verify-translations.js.map +1 -1
- package/dist/translations/core/translations-config.schema.json +8 -0
- package/package.json +1 -1
|
@@ -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
|
|
5
|
+
import * as path4 from "path";
|
|
6
6
|
|
|
7
|
-
// src/translations/utils/
|
|
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 =
|
|
27
|
-
if (!
|
|
100
|
+
const langPath = path2.join(translationsPath, language);
|
|
101
|
+
if (!fs2.existsSync(langPath)) {
|
|
28
102
|
return {};
|
|
29
103
|
}
|
|
30
|
-
const files =
|
|
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 =
|
|
34
|
-
const filePath =
|
|
35
|
-
const content =
|
|
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 =
|
|
47
|
-
if (!
|
|
48
|
-
|
|
120
|
+
const langPath = path2.join(translationsPath, language);
|
|
121
|
+
if (!fs2.existsSync(langPath)) {
|
|
122
|
+
fs2.mkdirSync(langPath, { recursive: true });
|
|
49
123
|
}
|
|
50
|
-
const filePath =
|
|
51
|
-
|
|
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 (!
|
|
129
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
56
130
|
return [];
|
|
57
131
|
}
|
|
58
|
-
return
|
|
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 =
|
|
62
|
-
if (!
|
|
135
|
+
const langPath = path2.join(translationsPath, language);
|
|
136
|
+
if (!fs2.existsSync(langPath)) {
|
|
63
137
|
return [];
|
|
64
138
|
}
|
|
65
|
-
return
|
|
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 (!
|
|
69
|
-
|
|
142
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
143
|
+
fs2.mkdirSync(translationsPath, { recursive: true });
|
|
70
144
|
}
|
|
71
145
|
for (const lang of languages) {
|
|
72
|
-
const langPath =
|
|
73
|
-
if (!
|
|
74
|
-
|
|
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 =
|
|
106
|
-
|
|
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 =
|
|
189
|
+
const filePath = path2.join(translationsPath, language, `${namespace}.json`);
|
|
116
190
|
const sourceFile = sourceTranslations[namespace] || {};
|
|
117
|
-
if (
|
|
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
|
|
162
|
-
import * as
|
|
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 =
|
|
503
|
-
if (
|
|
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 =
|
|
514
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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
|