mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 08:04:05 +00:00
124 lines
4.4 KiB
JavaScript
124 lines
4.4 KiB
JavaScript
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import path from 'node:path'
|
|
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { main as verifyMobileCatalog } from './verify-mobile-localization-catalog.mjs'
|
|
|
|
const LOCALES = ['en', 'es', 'ja', 'ko', 'zh']
|
|
|
|
function writeJson(filePath, value) {
|
|
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
|
}
|
|
|
|
function makeProject({ sourceText, catalogs }) {
|
|
const root = mkdtempSync(path.join(tmpdir(), 'orca-mobile-localization-'))
|
|
const appDirectory = path.join(root, 'mobile', 'app')
|
|
const sourceDirectory = path.join(root, 'mobile', 'src')
|
|
const localeDirectory = path.join(sourceDirectory, 'i18n', 'locales')
|
|
mkdirSync(appDirectory, { recursive: true })
|
|
mkdirSync(localeDirectory, { recursive: true })
|
|
writeFileSync(path.join(appDirectory, 'Example.tsx'), sourceText, 'utf8')
|
|
|
|
for (const locale of LOCALES) {
|
|
writeJson(path.join(localeDirectory, `${locale}.json`), catalogs?.[locale] ?? catalogs.en)
|
|
}
|
|
return root
|
|
}
|
|
|
|
async function runFailedVerification(root) {
|
|
const error = vi.spyOn(console, 'error').mockImplementation(() => undefined)
|
|
try {
|
|
await expect(verifyMobileCatalog(root)).resolves.toBe(1)
|
|
return error.mock.calls.flat().join('\n')
|
|
} finally {
|
|
error.mockRestore()
|
|
}
|
|
}
|
|
|
|
describe('verify-mobile-localization-catalog', () => {
|
|
it('verifies literal and conditional keys with matching options', async () => {
|
|
const catalog = { m: { greeting: 'Hello {{name}}', farewell: 'Bye {{name}}' } }
|
|
const root = makeProject({
|
|
sourceText:
|
|
"import { t } from '@/i18n/mobile-i18n'\nconst name = 'Orca'\nexport const label = t(name ? 'm.greeting' : 'm.farewell', { name })\n",
|
|
catalogs: { en: catalog }
|
|
})
|
|
|
|
await expect(verifyMobileCatalog(root)).resolves.toBe(0)
|
|
})
|
|
|
|
it('reports missing keys in conditional branches', async () => {
|
|
const root = makeProject({
|
|
sourceText:
|
|
"import { t } from '@/i18n/mobile-i18n'\nexport const label = t(flag ? 'm.known' : 'm.missing')\n",
|
|
catalogs: { en: { m: { known: 'Known' } } }
|
|
})
|
|
|
|
expect(await runFailedVerification(root)).toContain('missing English key: m.missing')
|
|
})
|
|
|
|
it('rejects translation keys that cannot be statically inspected', async () => {
|
|
const root = makeProject({
|
|
sourceText: "import { t } from '@/i18n/mobile-i18n'\nexport const label = t(runtimeKey)\n",
|
|
catalogs: { en: { m: { known: 'Known' } } }
|
|
})
|
|
|
|
expect(await runFailedVerification(root)).toContain('not statically inspectable')
|
|
})
|
|
|
|
it('requires call options to exactly match English placeholders', async () => {
|
|
const root = makeProject({
|
|
sourceText:
|
|
"import { t } from '@/i18n/mobile-i18n'\nexport const label = t('m.greeting', { value: 'Orca' })\n",
|
|
catalogs: { en: { m: { greeting: 'Hello {{name}}' } } }
|
|
})
|
|
|
|
const report = await runFailedVerification(root)
|
|
expect(report).toContain('options [value]')
|
|
expect(report).toContain('placeholders [name]')
|
|
})
|
|
|
|
it('allows missing translations in sparse locale catalogs', async () => {
|
|
const en = { m: { greeting: 'Hello {{name}}', farewell: 'Bye' } }
|
|
const root = makeProject({
|
|
sourceText:
|
|
"import { t } from '@/i18n/mobile-i18n'\nexport const label = t('m.greeting', { name: 'Orca' })\n",
|
|
catalogs: {
|
|
en,
|
|
es: { m: { greeting: 'Hola {{name}}' } }
|
|
}
|
|
})
|
|
|
|
await expect(verifyMobileCatalog(root)).resolves.toBe(0)
|
|
})
|
|
|
|
it('validates placeholders and extra keys in present translations', async () => {
|
|
const en = { m: { greeting: 'Hello {{name}}' } }
|
|
const root = makeProject({
|
|
sourceText:
|
|
"import { t } from '@/i18n/mobile-i18n'\nexport const label = t('m.greeting', { name: 'Orca' })\n",
|
|
catalogs: {
|
|
en,
|
|
es: { m: { greeting: 'Hola {{wrongName}}', extra: 'Extra' } }
|
|
}
|
|
})
|
|
|
|
const report = await runFailedVerification(root)
|
|
expect(report).toContain('es.json placeholder mismatch: m.greeting')
|
|
expect(report).toContain('es.json has extra key: m.extra')
|
|
})
|
|
|
|
it('rejects encoded HTML entities that React Native would render literally', async () => {
|
|
const root = makeProject({
|
|
sourceText: "export const label = 'not rendered'\n",
|
|
catalogs: { en: { m: { label: 'Don't encode & characters' } } }
|
|
})
|
|
|
|
expect(await runFailedVerification(root)).toContain(
|
|
'en.json: m.label contains an encoded HTML entity'
|
|
)
|
|
})
|
|
})
|