Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

check-i18n.test.ts 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. import fs from 'node:fs'
  2. import path from 'node:path'
  3. // Mock functions to simulate the check-i18n functionality
  4. const vm = require('node:vm')
  5. const transpile = require('typescript').transpile
  6. describe('check-i18n script functionality', () => {
  7. const testDir = path.join(__dirname, '../i18n-test')
  8. const testEnDir = path.join(testDir, 'en-US')
  9. const testZhDir = path.join(testDir, 'zh-Hans')
  10. // Helper function that replicates the getKeysFromLanguage logic
  11. async function getKeysFromLanguage(language: string, testPath = testDir): Promise<string[]> {
  12. return new Promise((resolve, reject) => {
  13. const folderPath = path.resolve(testPath, language)
  14. const allKeys: string[] = []
  15. if (!fs.existsSync(folderPath)) {
  16. resolve([])
  17. return
  18. }
  19. fs.readdir(folderPath, (err, files) => {
  20. if (err) {
  21. reject(err)
  22. return
  23. }
  24. const translationFiles = files.filter(file => /\.(ts|js)$/.test(file))
  25. translationFiles.forEach((file) => {
  26. const filePath = path.join(folderPath, file)
  27. const fileName = file.replace(/\.[^/.]+$/, '')
  28. const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) =>
  29. c.toUpperCase(),
  30. )
  31. try {
  32. const content = fs.readFileSync(filePath, 'utf8')
  33. const moduleExports = {}
  34. const context = {
  35. exports: moduleExports,
  36. module: { exports: moduleExports },
  37. require,
  38. console,
  39. __filename: filePath,
  40. __dirname: folderPath,
  41. }
  42. vm.runInNewContext(transpile(content), context)
  43. const translationObj = (context.module.exports as any).default || context.module.exports
  44. if (!translationObj || typeof translationObj !== 'object')
  45. throw new Error(`Error parsing file: ${filePath}`)
  46. const nestedKeys: string[] = []
  47. const iterateKeys = (obj: any, prefix = '') => {
  48. for (const key in obj) {
  49. const nestedKey = prefix ? `${prefix}.${key}` : key
  50. if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
  51. // This is an object (but not array), recurse into it but don't add it as a key
  52. iterateKeys(obj[key], nestedKey)
  53. }
  54. else {
  55. // This is a leaf node (string, number, boolean, array, etc.), add it as a key
  56. nestedKeys.push(nestedKey)
  57. }
  58. }
  59. }
  60. iterateKeys(translationObj)
  61. const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`)
  62. allKeys.push(...fileKeys)
  63. }
  64. catch (error) {
  65. reject(error)
  66. }
  67. })
  68. resolve(allKeys)
  69. })
  70. })
  71. }
  72. beforeEach(() => {
  73. // Clean up and create test directories
  74. if (fs.existsSync(testDir))
  75. fs.rmSync(testDir, { recursive: true })
  76. fs.mkdirSync(testDir, { recursive: true })
  77. fs.mkdirSync(testEnDir, { recursive: true })
  78. fs.mkdirSync(testZhDir, { recursive: true })
  79. })
  80. afterEach(() => {
  81. // Clean up test files
  82. if (fs.existsSync(testDir))
  83. fs.rmSync(testDir, { recursive: true })
  84. })
  85. describe('Key extraction logic', () => {
  86. it('should extract only leaf node keys, not intermediate objects', async () => {
  87. const testContent = `const translation = {
  88. simple: 'Simple Value',
  89. nested: {
  90. level1: 'Level 1 Value',
  91. deep: {
  92. level2: 'Level 2 Value'
  93. }
  94. },
  95. array: ['not extracted'],
  96. number: 42,
  97. boolean: true
  98. }
  99. export default translation
  100. `
  101. fs.writeFileSync(path.join(testEnDir, 'test.ts'), testContent)
  102. const keys = await getKeysFromLanguage('en-US')
  103. expect(keys).toEqual([
  104. 'test.simple',
  105. 'test.nested.level1',
  106. 'test.nested.deep.level2',
  107. 'test.array',
  108. 'test.number',
  109. 'test.boolean',
  110. ])
  111. // Should not include intermediate object keys
  112. expect(keys).not.toContain('test.nested')
  113. expect(keys).not.toContain('test.nested.deep')
  114. })
  115. it('should handle camelCase file name conversion correctly', async () => {
  116. const testContent = `const translation = {
  117. key: 'value'
  118. }
  119. export default translation
  120. `
  121. fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), testContent)
  122. fs.writeFileSync(path.join(testEnDir, 'user_profile.ts'), testContent)
  123. const keys = await getKeysFromLanguage('en-US')
  124. expect(keys).toContain('appDebug.key')
  125. expect(keys).toContain('userProfile.key')
  126. })
  127. })
  128. describe('Missing keys detection', () => {
  129. it('should detect missing keys in target language', async () => {
  130. const enContent = `const translation = {
  131. common: {
  132. save: 'Save',
  133. cancel: 'Cancel',
  134. delete: 'Delete'
  135. },
  136. app: {
  137. title: 'My App',
  138. version: '1.0'
  139. }
  140. }
  141. export default translation
  142. `
  143. const zhContent = `const translation = {
  144. common: {
  145. save: '保存',
  146. cancel: '取消'
  147. // missing 'delete'
  148. },
  149. app: {
  150. title: '我的应用'
  151. // missing 'version'
  152. }
  153. }
  154. export default translation
  155. `
  156. fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent)
  157. fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent)
  158. const enKeys = await getKeysFromLanguage('en-US')
  159. const zhKeys = await getKeysFromLanguage('zh-Hans')
  160. const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
  161. expect(missingKeys).toContain('test.common.delete')
  162. expect(missingKeys).toContain('test.app.version')
  163. expect(missingKeys).toHaveLength(2)
  164. })
  165. })
  166. describe('Extra keys detection', () => {
  167. it('should detect extra keys in target language', async () => {
  168. const enContent = `const translation = {
  169. common: {
  170. save: 'Save',
  171. cancel: 'Cancel'
  172. }
  173. }
  174. export default translation
  175. `
  176. const zhContent = `const translation = {
  177. common: {
  178. save: '保存',
  179. cancel: '取消',
  180. delete: '删除', // extra key
  181. extra: '额外的' // another extra key
  182. },
  183. newSection: {
  184. someKey: '某个值' // extra section
  185. }
  186. }
  187. export default translation
  188. `
  189. fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent)
  190. fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent)
  191. const enKeys = await getKeysFromLanguage('en-US')
  192. const zhKeys = await getKeysFromLanguage('zh-Hans')
  193. const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
  194. expect(extraKeys).toContain('test.common.delete')
  195. expect(extraKeys).toContain('test.common.extra')
  196. expect(extraKeys).toContain('test.newSection.someKey')
  197. expect(extraKeys).toHaveLength(3)
  198. })
  199. })
  200. describe('File filtering logic', () => {
  201. it('should filter keys by specific file correctly', async () => {
  202. // Create multiple files
  203. const file1Content = `const translation = {
  204. button: 'Button',
  205. text: 'Text'
  206. }
  207. export default translation
  208. `
  209. const file2Content = `const translation = {
  210. title: 'Title',
  211. description: 'Description'
  212. }
  213. export default translation
  214. `
  215. fs.writeFileSync(path.join(testEnDir, 'components.ts'), file1Content)
  216. fs.writeFileSync(path.join(testEnDir, 'pages.ts'), file2Content)
  217. fs.writeFileSync(path.join(testZhDir, 'components.ts'), file1Content)
  218. fs.writeFileSync(path.join(testZhDir, 'pages.ts'), file2Content)
  219. const allEnKeys = await getKeysFromLanguage('en-US')
  220. // Test file filtering logic
  221. const targetFile = 'components'
  222. const filteredEnKeys = allEnKeys.filter(key =>
  223. key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())),
  224. )
  225. expect(allEnKeys).toHaveLength(4) // 2 keys from each file
  226. expect(filteredEnKeys).toHaveLength(2) // only components keys
  227. expect(filteredEnKeys).toContain('components.button')
  228. expect(filteredEnKeys).toContain('components.text')
  229. expect(filteredEnKeys).not.toContain('pages.title')
  230. expect(filteredEnKeys).not.toContain('pages.description')
  231. })
  232. })
  233. describe('Complex nested structure handling', () => {
  234. it('should handle deeply nested objects correctly', async () => {
  235. const complexContent = `const translation = {
  236. level1: {
  237. level2: {
  238. level3: {
  239. level4: {
  240. deepValue: 'Deep Value'
  241. },
  242. anotherValue: 'Another Value'
  243. },
  244. simpleValue: 'Simple Value'
  245. },
  246. directValue: 'Direct Value'
  247. },
  248. rootValue: 'Root Value'
  249. }
  250. export default translation
  251. `
  252. fs.writeFileSync(path.join(testEnDir, 'complex.ts'), complexContent)
  253. const keys = await getKeysFromLanguage('en-US')
  254. expect(keys).toContain('complex.level1.level2.level3.level4.deepValue')
  255. expect(keys).toContain('complex.level1.level2.level3.anotherValue')
  256. expect(keys).toContain('complex.level1.level2.simpleValue')
  257. expect(keys).toContain('complex.level1.directValue')
  258. expect(keys).toContain('complex.rootValue')
  259. // Should not include intermediate objects
  260. expect(keys).not.toContain('complex.level1')
  261. expect(keys).not.toContain('complex.level1.level2')
  262. expect(keys).not.toContain('complex.level1.level2.level3')
  263. expect(keys).not.toContain('complex.level1.level2.level3.level4')
  264. })
  265. })
  266. describe('Edge cases', () => {
  267. it('should handle empty objects', async () => {
  268. const emptyContent = `const translation = {
  269. empty: {},
  270. withValue: 'value'
  271. }
  272. export default translation
  273. `
  274. fs.writeFileSync(path.join(testEnDir, 'empty.ts'), emptyContent)
  275. const keys = await getKeysFromLanguage('en-US')
  276. expect(keys).toContain('empty.withValue')
  277. expect(keys).not.toContain('empty.empty')
  278. })
  279. it('should handle special characters in keys', async () => {
  280. const specialContent = `const translation = {
  281. 'key-with-dash': 'value1',
  282. 'key_with_underscore': 'value2',
  283. 'key.with.dots': 'value3',
  284. normalKey: 'value4'
  285. }
  286. export default translation
  287. `
  288. fs.writeFileSync(path.join(testEnDir, 'special.ts'), specialContent)
  289. const keys = await getKeysFromLanguage('en-US')
  290. expect(keys).toContain('special.key-with-dash')
  291. expect(keys).toContain('special.key_with_underscore')
  292. expect(keys).toContain('special.key.with.dots')
  293. expect(keys).toContain('special.normalKey')
  294. })
  295. it('should handle different value types', async () => {
  296. const typesContent = `const translation = {
  297. stringValue: 'string',
  298. numberValue: 42,
  299. booleanValue: true,
  300. nullValue: null,
  301. undefinedValue: undefined,
  302. arrayValue: ['array', 'values'],
  303. objectValue: {
  304. nested: 'nested value'
  305. }
  306. }
  307. export default translation
  308. `
  309. fs.writeFileSync(path.join(testEnDir, 'types.ts'), typesContent)
  310. const keys = await getKeysFromLanguage('en-US')
  311. expect(keys).toContain('types.stringValue')
  312. expect(keys).toContain('types.numberValue')
  313. expect(keys).toContain('types.booleanValue')
  314. expect(keys).toContain('types.nullValue')
  315. expect(keys).toContain('types.undefinedValue')
  316. expect(keys).toContain('types.arrayValue')
  317. expect(keys).toContain('types.objectValue.nested')
  318. expect(keys).not.toContain('types.objectValue')
  319. })
  320. })
  321. describe('Real-world scenario tests', () => {
  322. it('should handle app-debug structure like real files', async () => {
  323. const appDebugEn = `const translation = {
  324. pageTitle: {
  325. line1: 'Prompt',
  326. line2: 'Engineering'
  327. },
  328. operation: {
  329. applyConfig: 'Publish',
  330. resetConfig: 'Reset',
  331. debugConfig: 'Debug'
  332. },
  333. generate: {
  334. instruction: 'Instructions',
  335. generate: 'Generate',
  336. resTitle: 'Generated Prompt',
  337. noDataLine1: 'Describe your use case on the left,',
  338. noDataLine2: 'the orchestration preview will show here.'
  339. }
  340. }
  341. export default translation
  342. `
  343. const appDebugZh = `const translation = {
  344. pageTitle: {
  345. line1: '提示词',
  346. line2: '编排'
  347. },
  348. operation: {
  349. applyConfig: '发布',
  350. resetConfig: '重置',
  351. debugConfig: '调试'
  352. },
  353. generate: {
  354. instruction: '指令',
  355. generate: '生成',
  356. resTitle: '生成的提示词',
  357. noData: '在左侧描述您的用例,编排预览将在此处显示。' // This is extra
  358. }
  359. }
  360. export default translation
  361. `
  362. fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), appDebugEn)
  363. fs.writeFileSync(path.join(testZhDir, 'app-debug.ts'), appDebugZh)
  364. const enKeys = await getKeysFromLanguage('en-US')
  365. const zhKeys = await getKeysFromLanguage('zh-Hans')
  366. const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
  367. const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
  368. expect(missingKeys).toContain('appDebug.generate.noDataLine1')
  369. expect(missingKeys).toContain('appDebug.generate.noDataLine2')
  370. expect(extraKeys).toContain('appDebug.generate.noData')
  371. expect(missingKeys).toHaveLength(2)
  372. expect(extraKeys).toHaveLength(1)
  373. })
  374. it('should handle time structure with operation nested keys', async () => {
  375. const timeEn = `const translation = {
  376. months: {
  377. January: 'January',
  378. February: 'February'
  379. },
  380. operation: {
  381. now: 'Now',
  382. ok: 'OK',
  383. cancel: 'Cancel',
  384. pickDate: 'Pick Date'
  385. },
  386. title: {
  387. pickTime: 'Pick Time'
  388. },
  389. defaultPlaceholder: 'Pick a time...'
  390. }
  391. export default translation
  392. `
  393. const timeZh = `const translation = {
  394. months: {
  395. January: '一月',
  396. February: '二月'
  397. },
  398. operation: {
  399. now: '此刻',
  400. ok: '确定',
  401. cancel: '取消',
  402. pickDate: '选择日期'
  403. },
  404. title: {
  405. pickTime: '选择时间'
  406. },
  407. pickDate: '选择日期', // This is extra - duplicates operation.pickDate
  408. defaultPlaceholder: '请选择时间...'
  409. }
  410. export default translation
  411. `
  412. fs.writeFileSync(path.join(testEnDir, 'time.ts'), timeEn)
  413. fs.writeFileSync(path.join(testZhDir, 'time.ts'), timeZh)
  414. const enKeys = await getKeysFromLanguage('en-US')
  415. const zhKeys = await getKeysFromLanguage('zh-Hans')
  416. const missingKeys = enKeys.filter(key => !zhKeys.includes(key))
  417. const extraKeys = zhKeys.filter(key => !enKeys.includes(key))
  418. expect(missingKeys).toHaveLength(0) // No missing keys
  419. expect(extraKeys).toContain('time.pickDate') // Extra root-level pickDate
  420. expect(extraKeys).toHaveLength(1)
  421. // Should have both keys available
  422. expect(zhKeys).toContain('time.operation.pickDate') // Correct nested key
  423. expect(zhKeys).toContain('time.pickDate') // Extra duplicate key
  424. })
  425. })
  426. describe('Statistics calculation', () => {
  427. it('should calculate correct difference statistics', async () => {
  428. const enContent = `const translation = {
  429. key1: 'value1',
  430. key2: 'value2',
  431. key3: 'value3'
  432. }
  433. export default translation
  434. `
  435. const zhContentMissing = `const translation = {
  436. key1: 'value1',
  437. key2: 'value2'
  438. // missing key3
  439. }
  440. export default translation
  441. `
  442. const zhContentExtra = `const translation = {
  443. key1: 'value1',
  444. key2: 'value2',
  445. key3: 'value3',
  446. key4: 'extra',
  447. key5: 'extra2'
  448. }
  449. export default translation
  450. `
  451. fs.writeFileSync(path.join(testEnDir, 'stats.ts'), enContent)
  452. // Test missing keys scenario
  453. fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentMissing)
  454. const enKeys = await getKeysFromLanguage('en-US')
  455. const zhKeysMissing = await getKeysFromLanguage('zh-Hans')
  456. expect(enKeys.length - zhKeysMissing.length).toBe(1) // +1 means 1 missing key
  457. // Test extra keys scenario
  458. fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentExtra)
  459. const zhKeysExtra = await getKeysFromLanguage('zh-Hans')
  460. expect(enKeys.length - zhKeysExtra.length).toBe(-2) // -2 means 2 extra keys
  461. })
  462. })
  463. describe('Auto-remove multiline key-value pairs', () => {
  464. // Helper function to simulate removeExtraKeysFromFile logic
  465. function removeExtraKeysFromFile(content: string, keysToRemove: string[]): string {
  466. const lines = content.split('\n')
  467. const linesToRemove: number[] = []
  468. for (const keyToRemove of keysToRemove) {
  469. let targetLineIndex = -1
  470. const linesToRemoveForKey: number[] = []
  471. // Find the key line (simplified for single-level keys in test)
  472. for (let i = 0; i < lines.length; i++) {
  473. const line = lines[i]
  474. const keyPattern = new RegExp(`^\\s*${keyToRemove}\\s*:`)
  475. if (keyPattern.test(line)) {
  476. targetLineIndex = i
  477. break
  478. }
  479. }
  480. if (targetLineIndex !== -1) {
  481. linesToRemoveForKey.push(targetLineIndex)
  482. // Check if this is a multiline key-value pair
  483. const keyLine = lines[targetLineIndex]
  484. const trimmedKeyLine = keyLine.trim()
  485. // If key line ends with ":" (not complete value), it's likely multiline
  486. if (trimmedKeyLine.endsWith(':') && !trimmedKeyLine.includes('{') && !trimmedKeyLine.match(/:\s*['"`]/)) {
  487. // Find the value lines that belong to this key
  488. let currentLine = targetLineIndex + 1
  489. let foundValue = false
  490. while (currentLine < lines.length) {
  491. const line = lines[currentLine]
  492. const trimmed = line.trim()
  493. // Skip empty lines
  494. if (trimmed === '') {
  495. currentLine++
  496. continue
  497. }
  498. // Check if this line starts a new key (indicates end of current value)
  499. if (trimmed.match(/^\w+\s*:/))
  500. break
  501. // Check if this line is part of the value
  502. if (trimmed.startsWith('\'') || trimmed.startsWith('"') || trimmed.startsWith('`') || foundValue) {
  503. linesToRemoveForKey.push(currentLine)
  504. foundValue = true
  505. // Check if this line ends the value (ends with quote and comma/no comma)
  506. if ((trimmed.endsWith('\',') || trimmed.endsWith('",') || trimmed.endsWith('`,')
  507. || trimmed.endsWith('\'') || trimmed.endsWith('"') || trimmed.endsWith('`'))
  508. && !trimmed.startsWith('//'))
  509. break
  510. }
  511. else {
  512. break
  513. }
  514. currentLine++
  515. }
  516. }
  517. linesToRemove.push(...linesToRemoveForKey)
  518. }
  519. }
  520. // Remove duplicates and sort in reverse order
  521. const uniqueLinesToRemove = [...new Set(linesToRemove)].sort((a, b) => b - a)
  522. for (const lineIndex of uniqueLinesToRemove)
  523. lines.splice(lineIndex, 1)
  524. return lines.join('\n')
  525. }
  526. it('should remove single-line key-value pairs correctly', () => {
  527. const content = `const translation = {
  528. keepThis: 'This should stay',
  529. removeThis: 'This should be removed',
  530. alsoKeep: 'This should also stay',
  531. }
  532. export default translation`
  533. const result = removeExtraKeysFromFile(content, ['removeThis'])
  534. expect(result).toContain('keepThis: \'This should stay\'')
  535. expect(result).toContain('alsoKeep: \'This should also stay\'')
  536. expect(result).not.toContain('removeThis: \'This should be removed\'')
  537. })
  538. it('should remove multiline key-value pairs completely', () => {
  539. const content = `const translation = {
  540. keepThis: 'This should stay',
  541. removeMultiline:
  542. 'This is a multiline value that should be removed completely',
  543. alsoKeep: 'This should also stay',
  544. }
  545. export default translation`
  546. const result = removeExtraKeysFromFile(content, ['removeMultiline'])
  547. expect(result).toContain('keepThis: \'This should stay\'')
  548. expect(result).toContain('alsoKeep: \'This should also stay\'')
  549. expect(result).not.toContain('removeMultiline:')
  550. expect(result).not.toContain('This is a multiline value that should be removed completely')
  551. })
  552. it('should handle mixed single-line and multiline removals', () => {
  553. const content = `const translation = {
  554. keepThis: 'Keep this',
  555. removeSingle: 'Remove this single line',
  556. removeMultiline:
  557. 'Remove this multiline value',
  558. anotherMultiline:
  559. 'Another multiline that spans multiple lines',
  560. keepAnother: 'Keep this too',
  561. }
  562. export default translation`
  563. const result = removeExtraKeysFromFile(content, ['removeSingle', 'removeMultiline', 'anotherMultiline'])
  564. expect(result).toContain('keepThis: \'Keep this\'')
  565. expect(result).toContain('keepAnother: \'Keep this too\'')
  566. expect(result).not.toContain('removeSingle:')
  567. expect(result).not.toContain('removeMultiline:')
  568. expect(result).not.toContain('anotherMultiline:')
  569. expect(result).not.toContain('Remove this single line')
  570. expect(result).not.toContain('Remove this multiline value')
  571. expect(result).not.toContain('Another multiline that spans multiple lines')
  572. })
  573. it('should properly detect multiline vs single-line patterns', () => {
  574. const multilineContent = `const translation = {
  575. singleLine: 'This is single line',
  576. multilineKey:
  577. 'This is multiline',
  578. keyWithColon: 'Value with: colon inside',
  579. objectKey: {
  580. nested: 'value'
  581. },
  582. }
  583. export default translation`
  584. // Test that single line with colon in value is not treated as multiline
  585. const result1 = removeExtraKeysFromFile(multilineContent, ['keyWithColon'])
  586. expect(result1).not.toContain('keyWithColon:')
  587. expect(result1).not.toContain('Value with: colon inside')
  588. // Test that true multiline is handled correctly
  589. const result2 = removeExtraKeysFromFile(multilineContent, ['multilineKey'])
  590. expect(result2).not.toContain('multilineKey:')
  591. expect(result2).not.toContain('This is multiline')
  592. // Test that object key removal works (note: this is a simplified test)
  593. // In real scenario, object removal would be more complex
  594. const result3 = removeExtraKeysFromFile(multilineContent, ['objectKey'])
  595. expect(result3).not.toContain('objectKey: {')
  596. // Note: Our simplified test function doesn't handle nested object removal perfectly
  597. // This is acceptable as it's testing the main multiline string removal functionality
  598. })
  599. it('should handle real-world Polish translation structure', () => {
  600. const polishContent = `const translation = {
  601. createApp: 'UTWÓRZ APLIKACJĘ',
  602. newApp: {
  603. captionAppType: 'Jaki typ aplikacji chcesz stworzyć?',
  604. chatbotDescription:
  605. 'Zbuduj aplikację opartą na czacie. Ta aplikacja używa formatu pytań i odpowiedzi.',
  606. agentDescription:
  607. 'Zbuduj inteligentnego agenta, który może autonomicznie wybierać narzędzia.',
  608. basic: 'Podstawowy',
  609. },
  610. }
  611. export default translation`
  612. const result = removeExtraKeysFromFile(polishContent, ['captionAppType', 'chatbotDescription', 'agentDescription'])
  613. expect(result).toContain('createApp: \'UTWÓRZ APLIKACJĘ\'')
  614. expect(result).toContain('basic: \'Podstawowy\'')
  615. expect(result).not.toContain('captionAppType:')
  616. expect(result).not.toContain('chatbotDescription:')
  617. expect(result).not.toContain('agentDescription:')
  618. expect(result).not.toContain('Jaki typ aplikacji')
  619. expect(result).not.toContain('Zbuduj aplikację opartą na czacie')
  620. expect(result).not.toContain('Zbuduj inteligentnego agenta')
  621. })
  622. })
  623. })