| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 | 
							- import fs from 'node:fs'
 - import path from 'node:path'
 - 
 - // Mock functions to simulate the check-i18n functionality
 - const vm = require('node:vm')
 - const transpile = require('typescript').transpile
 - 
 - describe('check-i18n script functionality', () => {
 -   const testDir = path.join(__dirname, '../i18n-test')
 -   const testEnDir = path.join(testDir, 'en-US')
 -   const testZhDir = path.join(testDir, 'zh-Hans')
 - 
 -   // Helper function that replicates the getKeysFromLanguage logic
 -   async function getKeysFromLanguage(language: string, testPath = testDir): Promise<string[]> {
 -     return new Promise((resolve, reject) => {
 -       const folderPath = path.resolve(testPath, language)
 -       const allKeys: string[] = []
 - 
 -       if (!fs.existsSync(folderPath)) {
 -         resolve([])
 -         return
 -       }
 - 
 -       fs.readdir(folderPath, (err, files) => {
 -         if (err) {
 -           reject(err)
 -           return
 -         }
 - 
 -         const translationFiles = files.filter(file => /\.(ts|js)$/.test(file))
 - 
 -         translationFiles.forEach((file) => {
 -           const filePath = path.join(folderPath, file)
 -           const fileName = file.replace(/\.[^/.]+$/, '')
 -           const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) =>
 -             c.toUpperCase(),
 -           )
 - 
 -           try {
 -             const content = fs.readFileSync(filePath, 'utf8')
 -             const moduleExports = {}
 -             const context = {
 -               exports: moduleExports,
 -               module: { exports: moduleExports },
 -               require,
 -               console,
 -               __filename: filePath,
 -               __dirname: folderPath,
 -             }
 - 
 -             vm.runInNewContext(transpile(content), context)
 -             const translationObj = (context.module.exports as any).default || context.module.exports
 - 
 -             if (!translationObj || typeof translationObj !== 'object')
 -               throw new Error(`Error parsing file: ${filePath}`)
 - 
 -             const nestedKeys: string[] = []
 -             const iterateKeys = (obj: any, prefix = '') => {
 -               for (const key in obj) {
 -                 const nestedKey = prefix ? `${prefix}.${key}` : key
 -                 if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
 -                   // This is an object (but not array), recurse into it but don't add it as a key
 -                   iterateKeys(obj[key], nestedKey)
 -                 }
 -                 else {
 -                   // This is a leaf node (string, number, boolean, array, etc.), add it as a key
 -                   nestedKeys.push(nestedKey)
 -                 }
 -               }
 -             }
 -             iterateKeys(translationObj)
 - 
 -             const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`)
 -             allKeys.push(...fileKeys)
 -           }
 -           catch (error) {
 -             reject(error)
 -           }
 -         })
 -         resolve(allKeys)
 -       })
 -     })
 -   }
 - 
 -   beforeEach(() => {
 -     // Clean up and create test directories
 -     if (fs.existsSync(testDir))
 -       fs.rmSync(testDir, { recursive: true })
 - 
 -     fs.mkdirSync(testDir, { recursive: true })
 -     fs.mkdirSync(testEnDir, { recursive: true })
 -     fs.mkdirSync(testZhDir, { recursive: true })
 -   })
 - 
 -   afterEach(() => {
 -     // Clean up test files
 -     if (fs.existsSync(testDir))
 -       fs.rmSync(testDir, { recursive: true })
 -   })
 - 
 -   describe('Key extraction logic', () => {
 -     it('should extract only leaf node keys, not intermediate objects', async () => {
 -       const testContent = `const translation = {
 -   simple: 'Simple Value',
 -   nested: {
 -     level1: 'Level 1 Value',
 -     deep: {
 -       level2: 'Level 2 Value'
 -     }
 -   },
 -   array: ['not extracted'],
 -   number: 42,
 -   boolean: true
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'test.ts'), testContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toEqual([
 -         'test.simple',
 -         'test.nested.level1',
 -         'test.nested.deep.level2',
 -         'test.array',
 -         'test.number',
 -         'test.boolean',
 -       ])
 - 
 -       // Should not include intermediate object keys
 -       expect(keys).not.toContain('test.nested')
 -       expect(keys).not.toContain('test.nested.deep')
 -     })
 - 
 -     it('should handle camelCase file name conversion correctly', async () => {
 -       const testContent = `const translation = {
 -   key: 'value'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), testContent)
 -       fs.writeFileSync(path.join(testEnDir, 'user_profile.ts'), testContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toContain('appDebug.key')
 -       expect(keys).toContain('userProfile.key')
 -     })
 -   })
 - 
 -   describe('Missing keys detection', () => {
 -     it('should detect missing keys in target language', async () => {
 -       const enContent = `const translation = {
 -   common: {
 -     save: 'Save',
 -     cancel: 'Cancel',
 -     delete: 'Delete'
 -   },
 -   app: {
 -     title: 'My App',
 -     version: '1.0'
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       const zhContent = `const translation = {
 -   common: {
 -     save: '保存',
 -     cancel: '取消'
 -     // missing 'delete'
 -   },
 -   app: {
 -     title: '我的应用'
 -     // missing 'version'
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent)
 -       fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent)
 - 
 -       const enKeys = await getKeysFromLanguage('en-US')
 -       const zhKeys = await getKeysFromLanguage('zh-Hans')
 - 
 -       const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
 - 
 -       expect(missingKeys).toContain('test.common.delete')
 -       expect(missingKeys).toContain('test.app.version')
 -       expect(missingKeys).toHaveLength(2)
 -     })
 -   })
 - 
 -   describe('Extra keys detection', () => {
 -     it('should detect extra keys in target language', async () => {
 -       const enContent = `const translation = {
 -   common: {
 -     save: 'Save',
 -     cancel: 'Cancel'
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       const zhContent = `const translation = {
 -   common: {
 -     save: '保存',
 -     cancel: '取消',
 -     delete: '删除', // extra key
 -     extra: '额外的' // another extra key
 -   },
 -   newSection: {
 -     someKey: '某个值' // extra section
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent)
 -       fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent)
 - 
 -       const enKeys = await getKeysFromLanguage('en-US')
 -       const zhKeys = await getKeysFromLanguage('zh-Hans')
 - 
 -       const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
 - 
 -       expect(extraKeys).toContain('test.common.delete')
 -       expect(extraKeys).toContain('test.common.extra')
 -       expect(extraKeys).toContain('test.newSection.someKey')
 -       expect(extraKeys).toHaveLength(3)
 -     })
 -   })
 - 
 -   describe('File filtering logic', () => {
 -     it('should filter keys by specific file correctly', async () => {
 -       // Create multiple files
 -       const file1Content = `const translation = {
 -   button: 'Button',
 -   text: 'Text'
 - }
 - 
 - export default translation
 - `
 - 
 -       const file2Content = `const translation = {
 -   title: 'Title',
 -   description: 'Description'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'components.ts'), file1Content)
 -       fs.writeFileSync(path.join(testEnDir, 'pages.ts'), file2Content)
 -       fs.writeFileSync(path.join(testZhDir, 'components.ts'), file1Content)
 -       fs.writeFileSync(path.join(testZhDir, 'pages.ts'), file2Content)
 - 
 -       const allEnKeys = await getKeysFromLanguage('en-US')
 -       const allZhKeys = await getKeysFromLanguage('zh-Hans')
 - 
 -       // Test file filtering logic
 -       const targetFile = 'components'
 -       const filteredEnKeys = allEnKeys.filter(key =>
 -         key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())),
 -       )
 - 
 -       expect(allEnKeys).toHaveLength(4) // 2 keys from each file
 -       expect(filteredEnKeys).toHaveLength(2) // only components keys
 -       expect(filteredEnKeys).toContain('components.button')
 -       expect(filteredEnKeys).toContain('components.text')
 -       expect(filteredEnKeys).not.toContain('pages.title')
 -       expect(filteredEnKeys).not.toContain('pages.description')
 -     })
 -   })
 - 
 -   describe('Complex nested structure handling', () => {
 -     it('should handle deeply nested objects correctly', async () => {
 -       const complexContent = `const translation = {
 -   level1: {
 -     level2: {
 -       level3: {
 -         level4: {
 -           deepValue: 'Deep Value'
 -         },
 -         anotherValue: 'Another Value'
 -       },
 -       simpleValue: 'Simple Value'
 -     },
 -     directValue: 'Direct Value'
 -   },
 -   rootValue: 'Root Value'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'complex.ts'), complexContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toContain('complex.level1.level2.level3.level4.deepValue')
 -       expect(keys).toContain('complex.level1.level2.level3.anotherValue')
 -       expect(keys).toContain('complex.level1.level2.simpleValue')
 -       expect(keys).toContain('complex.level1.directValue')
 -       expect(keys).toContain('complex.rootValue')
 - 
 -       // Should not include intermediate objects
 -       expect(keys).not.toContain('complex.level1')
 -       expect(keys).not.toContain('complex.level1.level2')
 -       expect(keys).not.toContain('complex.level1.level2.level3')
 -       expect(keys).not.toContain('complex.level1.level2.level3.level4')
 -     })
 -   })
 - 
 -   describe('Edge cases', () => {
 -     it('should handle empty objects', async () => {
 -       const emptyContent = `const translation = {
 -   empty: {},
 -   withValue: 'value'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'empty.ts'), emptyContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toContain('empty.withValue')
 -       expect(keys).not.toContain('empty.empty')
 -     })
 - 
 -     it('should handle special characters in keys', async () => {
 -       const specialContent = `const translation = {
 -   'key-with-dash': 'value1',
 -   'key_with_underscore': 'value2',
 -   'key.with.dots': 'value3',
 -   normalKey: 'value4'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'special.ts'), specialContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toContain('special.key-with-dash')
 -       expect(keys).toContain('special.key_with_underscore')
 -       expect(keys).toContain('special.key.with.dots')
 -       expect(keys).toContain('special.normalKey')
 -     })
 - 
 -     it('should handle different value types', async () => {
 -       const typesContent = `const translation = {
 -   stringValue: 'string',
 -   numberValue: 42,
 -   booleanValue: true,
 -   nullValue: null,
 -   undefinedValue: undefined,
 -   arrayValue: ['array', 'values'],
 -   objectValue: {
 -     nested: 'nested value'
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'types.ts'), typesContent)
 - 
 -       const keys = await getKeysFromLanguage('en-US')
 - 
 -       expect(keys).toContain('types.stringValue')
 -       expect(keys).toContain('types.numberValue')
 -       expect(keys).toContain('types.booleanValue')
 -       expect(keys).toContain('types.nullValue')
 -       expect(keys).toContain('types.undefinedValue')
 -       expect(keys).toContain('types.arrayValue')
 -       expect(keys).toContain('types.objectValue.nested')
 -       expect(keys).not.toContain('types.objectValue')
 -     })
 -   })
 - 
 -   describe('Real-world scenario tests', () => {
 -     it('should handle app-debug structure like real files', async () => {
 -       const appDebugEn = `const translation = {
 -   pageTitle: {
 -     line1: 'Prompt',
 -     line2: 'Engineering'
 -   },
 -   operation: {
 -     applyConfig: 'Publish',
 -     resetConfig: 'Reset',
 -     debugConfig: 'Debug'
 -   },
 -   generate: {
 -     instruction: 'Instructions',
 -     generate: 'Generate',
 -     resTitle: 'Generated Prompt',
 -     noDataLine1: 'Describe your use case on the left,',
 -     noDataLine2: 'the orchestration preview will show here.'
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       const appDebugZh = `const translation = {
 -   pageTitle: {
 -     line1: '提示词',
 -     line2: '编排'
 -   },
 -   operation: {
 -     applyConfig: '发布',
 -     resetConfig: '重置',
 -     debugConfig: '调试'
 -   },
 -   generate: {
 -     instruction: '指令',
 -     generate: '生成',
 -     resTitle: '生成的提示词',
 -     noData: '在左侧描述您的用例,编排预览将在此处显示。' // This is extra
 -   }
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), appDebugEn)
 -       fs.writeFileSync(path.join(testZhDir, 'app-debug.ts'), appDebugZh)
 - 
 -       const enKeys = await getKeysFromLanguage('en-US')
 -       const zhKeys = await getKeysFromLanguage('zh-Hans')
 - 
 -       const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
 -       const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
 - 
 -       expect(missingKeys).toContain('appDebug.generate.noDataLine1')
 -       expect(missingKeys).toContain('appDebug.generate.noDataLine2')
 -       expect(extraKeys).toContain('appDebug.generate.noData')
 - 
 -       expect(missingKeys).toHaveLength(2)
 -       expect(extraKeys).toHaveLength(1)
 -     })
 - 
 -     it('should handle time structure with operation nested keys', async () => {
 -       const timeEn = `const translation = {
 -   months: {
 -     January: 'January',
 -     February: 'February'
 -   },
 -   operation: {
 -     now: 'Now',
 -     ok: 'OK',
 -     cancel: 'Cancel',
 -     pickDate: 'Pick Date'
 -   },
 -   title: {
 -     pickTime: 'Pick Time'
 -   },
 -   defaultPlaceholder: 'Pick a time...'
 - }
 - 
 - export default translation
 - `
 - 
 -       const timeZh = `const translation = {
 -   months: {
 -     January: '一月',
 -     February: '二月'
 -   },
 -   operation: {
 -     now: '此刻',
 -     ok: '确定',
 -     cancel: '取消',
 -     pickDate: '选择日期'
 -   },
 -   title: {
 -     pickTime: '选择时间'
 -   },
 -   pickDate: '选择日期', // This is extra - duplicates operation.pickDate
 -   defaultPlaceholder: '请选择时间...'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'time.ts'), timeEn)
 -       fs.writeFileSync(path.join(testZhDir, 'time.ts'), timeZh)
 - 
 -       const enKeys = await getKeysFromLanguage('en-US')
 -       const zhKeys = await getKeysFromLanguage('zh-Hans')
 - 
 -       const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
 -       const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
 - 
 -       expect(missingKeys).toHaveLength(0) // No missing keys
 -       expect(extraKeys).toContain('time.pickDate') // Extra root-level pickDate
 -       expect(extraKeys).toHaveLength(1)
 - 
 -       // Should have both keys available
 -       expect(zhKeys).toContain('time.operation.pickDate') // Correct nested key
 -       expect(zhKeys).toContain('time.pickDate') // Extra duplicate key
 -     })
 -   })
 - 
 -   describe('Statistics calculation', () => {
 -     it('should calculate correct difference statistics', async () => {
 -       const enContent = `const translation = {
 -   key1: 'value1',
 -   key2: 'value2',
 -   key3: 'value3'
 - }
 - 
 - export default translation
 - `
 - 
 -       const zhContentMissing = `const translation = {
 -   key1: 'value1',
 -   key2: 'value2'
 -   // missing key3
 - }
 - 
 - export default translation
 - `
 - 
 -       const zhContentExtra = `const translation = {
 -   key1: 'value1',
 -   key2: 'value2', 
 -   key3: 'value3',
 -   key4: 'extra',
 -   key5: 'extra2'
 - }
 - 
 - export default translation
 - `
 - 
 -       fs.writeFileSync(path.join(testEnDir, 'stats.ts'), enContent)
 - 
 -       // Test missing keys scenario
 -       fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentMissing)
 - 
 -       const enKeys = await getKeysFromLanguage('en-US')
 -       const zhKeysMissing = await getKeysFromLanguage('zh-Hans')
 - 
 -       expect(enKeys.length - zhKeysMissing.length).toBe(1) // +1 means 1 missing key
 - 
 -       // Test extra keys scenario
 -       fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentExtra)
 - 
 -       const zhKeysExtra = await getKeysFromLanguage('zh-Hans')
 - 
 -       expect(enKeys.length - zhKeysExtra.length).toBe(-2) // -2 means 2 extra keys
 -     })
 -   })
 - })
 
 
  |