You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

check-i18n-sync.js 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/usr/bin/env node
  2. const fs = require('fs')
  3. const path = require('path')
  4. const { camelCase } = require('lodash')
  5. // Import the NAMESPACES array from i18next-config.ts
  6. function getNamespacesFromConfig() {
  7. const configPath = path.join(__dirname, 'i18next-config.ts')
  8. const configContent = fs.readFileSync(configPath, 'utf8')
  9. // Extract NAMESPACES array using regex
  10. const namespacesMatch = configContent.match(/const NAMESPACES = \[([\s\S]*?)\]/)
  11. if (!namespacesMatch) {
  12. throw new Error('Could not find NAMESPACES array in i18next-config.ts')
  13. }
  14. // Parse the namespaces
  15. const namespacesStr = namespacesMatch[1]
  16. const namespaces = namespacesStr
  17. .split(',')
  18. .map(line => line.trim())
  19. .filter(line => line.startsWith("'") || line.startsWith('"'))
  20. .map(line => line.slice(1, -1)) // Remove quotes
  21. return namespaces
  22. }
  23. function getNamespacesFromTypes() {
  24. const typesPath = path.join(__dirname, '../types/i18n.d.ts')
  25. if (!fs.existsSync(typesPath)) {
  26. return null
  27. }
  28. const typesContent = fs.readFileSync(typesPath, 'utf8')
  29. // Extract namespaces from Messages type
  30. const messagesMatch = typesContent.match(/export type Messages = \{([\s\S]*?)\}/)
  31. if (!messagesMatch) {
  32. return null
  33. }
  34. // Parse the properties
  35. const propertiesStr = messagesMatch[1]
  36. const properties = propertiesStr
  37. .split('\n')
  38. .map(line => line.trim())
  39. .filter(line => line.includes(':'))
  40. .map(line => line.split(':')[0].trim())
  41. .filter(prop => prop.length > 0)
  42. return properties
  43. }
  44. function main() {
  45. try {
  46. console.log('🔍 Checking i18n types synchronization...')
  47. // Get namespaces from config
  48. const configNamespaces = getNamespacesFromConfig()
  49. console.log(`📦 Found ${configNamespaces.length} namespaces in config`)
  50. // Convert to camelCase for comparison
  51. const configCamelCase = configNamespaces.map(ns => camelCase(ns)).sort()
  52. // Get namespaces from type definitions
  53. const typeNamespaces = getNamespacesFromTypes()
  54. if (!typeNamespaces) {
  55. console.error('❌ Type definitions file not found or invalid')
  56. console.error(' Run: pnpm run gen:i18n-types')
  57. process.exit(1)
  58. }
  59. console.log(`🔧 Found ${typeNamespaces.length} namespaces in types`)
  60. const typeCamelCase = typeNamespaces.sort()
  61. // Compare arrays
  62. const configSet = new Set(configCamelCase)
  63. const typeSet = new Set(typeCamelCase)
  64. // Find missing in types
  65. const missingInTypes = configCamelCase.filter(ns => !typeSet.has(ns))
  66. // Find extra in types
  67. const extraInTypes = typeCamelCase.filter(ns => !configSet.has(ns))
  68. let hasErrors = false
  69. if (missingInTypes.length > 0) {
  70. hasErrors = true
  71. console.error('❌ Missing in type definitions:')
  72. missingInTypes.forEach(ns => console.error(` - ${ns}`))
  73. }
  74. if (extraInTypes.length > 0) {
  75. hasErrors = true
  76. console.error('❌ Extra in type definitions:')
  77. extraInTypes.forEach(ns => console.error(` - ${ns}`))
  78. }
  79. if (hasErrors) {
  80. console.error('\n💡 To fix synchronization issues:')
  81. console.error(' Run: pnpm run gen:i18n-types')
  82. process.exit(1)
  83. }
  84. console.log('✅ i18n types are synchronized')
  85. } catch (error) {
  86. console.error('❌ Error:', error.message)
  87. process.exit(1)
  88. }
  89. }
  90. if (require.main === module) {
  91. main()
  92. }