`verify`: Checks for stale or missing translations.
()
| 208 | |
| 209 | /** `verify`: Checks for stale or missing translations. */ |
| 210 | async function verify() { |
| 211 | console.log('Verifying i18n status...'); |
| 212 | const manifest = loadJson(MANIFEST_PATH); |
| 213 | const state = loadJson(STATE_PATH); |
| 214 | |
| 215 | if (Object.keys(manifest).length === 0) { |
| 216 | console.error('❌ Error: No manifest found. Run "npm run i18n:sync" first.'); |
| 217 | process.exit(1); |
| 218 | } |
| 219 | |
| 220 | const staleTranslations = []; |
| 221 | const missingTranslations = []; |
| 222 | const localeStats = {}; |
| 223 | |
| 224 | for (const locale in state) { |
| 225 | localeStats[locale] = { total: 0, translated: 0, stale: 0 }; |
| 226 | |
| 227 | for (const key in manifest) { |
| 228 | const sourceHash = manifest[key]; |
| 229 | const entry = normalizeStateEntry(state[locale]?.[key]); |
| 230 | |
| 231 | localeStats[locale].total++; |
| 232 | |
| 233 | if (!state[locale]?.[key] || !entry.translation) { |
| 234 | missingTranslations.push({ locale, key }); |
| 235 | } else if (entry.source !== sourceHash) { |
| 236 | staleTranslations.push({ locale, key }); |
| 237 | localeStats[locale].stale++; |
| 238 | } else { |
| 239 | localeStats[locale].translated++; |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | // Print statistics |
| 245 | console.log('\n📊 Translation Statistics:'); |
| 246 | for (const [locale, stats] of Object.entries(localeStats)) { |
| 247 | const percentage = Math.round((stats.translated / stats.total) * 100); |
| 248 | console.log(` ${locale}: ${stats.translated}/${stats.total} (${percentage}%) up-to-date, ${stats.stale} stale`); |
| 249 | } |
| 250 | |
| 251 | const hasIssues = staleTranslations.length > 0 || missingTranslations.length > 0; |
| 252 | |
| 253 | if (missingTranslations.length > 0) { |
| 254 | console.error('\n❌ Missing translations:'); |
| 255 | const byLocale = {}; |
| 256 | missingTranslations.forEach(({ locale, key }) => { |
| 257 | if (!byLocale[locale]) byLocale[locale] = []; |
| 258 | byLocale[locale].push(key); |
| 259 | }); |
| 260 | |
| 261 | for (const [locale, keys] of Object.entries(byLocale)) { |
| 262 | console.error(` [${locale}] ${keys.length} missing keys:`); |
| 263 | keys.forEach(key => console.error(` - ${key}`)); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | if (staleTranslations.length > 0) { |
no test coverage detected