Files
Hermes-ui/tests/client/i18n-coverage.test.ts
T
Zhicheng Han 03c18c210d 修复侧边栏 i18n 缺失 key 警告 (#170)
* Fix i18n missing-key warnings

* Add locale translations for i18n warning keys
2026-04-24 08:31:42 +08:00

73 lines
2.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { readdirSync, readFileSync } from 'fs'
import { join, relative } from 'path'
import { changelog } from '@/data/changelog'
import { messages, rawMessages } from '@/i18n/messages'
const SOURCE_ROOT = join(process.cwd(), 'packages/client/src')
function walkFiles(dir: string, files: string[] = []): string[] {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name)
if (entry.isDirectory()) {
walkFiles(path, files)
} else if (/\.(ts|vue)$/.test(entry.name) && !path.includes('/i18n/locales/')) {
files.push(path)
}
}
return files
}
function collectLiteralTranslationKeys(): string[] {
const keys = new Set<string>()
const translationCall = /(?:\b|\$)t\(\s*['"]([^'"]+)['"]/g
for (const file of walkFiles(SOURCE_ROOT)) {
const source = readFileSync(file, 'utf8')
for (const match of source.matchAll(translationCall)) {
keys.add(match[1])
}
}
for (const entry of changelog) {
for (const change of entry.changes) {
keys.add(change)
}
}
return [...keys].sort()
}
function hasPath(messages: Record<string, unknown>, key: string): boolean {
let current: unknown = messages
for (const part of key.split('.')) {
if (!current || typeof current !== 'object' || !(part in current)) return false
current = (current as Record<string, unknown>)[part]
}
return typeof current !== 'undefined'
}
describe('i18n locale coverage', () => {
it('defines every statically referenced translation key in the English source locale', () => {
const missing = collectLiteralTranslationKeys().filter((key) => !hasPath(rawMessages.en, key))
expect(missing).toEqual([])
})
it('defines every statically referenced translation key in effective runtime messages', () => {
const requiredKeys = collectLiteralTranslationKeys()
const missing = Object.entries(messages).flatMap(([locale, localeMessages]) =>
requiredKeys
.filter((key) => !hasPath(localeMessages, key))
.map((key) => `${locale}: ${key}`),
)
expect(missing).toEqual([])
})
it('keeps the coverage scanner rooted in client source files', () => {
expect(relative(process.cwd(), SOURCE_ROOT)).toBe('packages/client/src')
})
})