// `storebound shots copy translate | check | reviewed`: fills copy.json per language with a marker file, caption limits. import { describe, expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import type { ShipConfig } from '../cli/lib/config'; import { validateCopy } from '../cli/shots/lib'; import { applyFields, marksNotes, overLimit, parseMarks, sceneFields } from '../cli/lib/shots-copy-translate'; import { run } from '../cli/shots/copy'; import { BASE, capture, project } from './helpers/store-mock'; const CFG: ShipConfig = { ...BASE, languages: [{ id: 'en', apple: 'en-US', play: 'en-US' }, { id: 'de', apple: 'de-DE', play: 'de-DE' }], shots: { copy: 'store/shots/copy.json', scenes: [{ id: 'home', palette: 'gold', frames: [] }] } as never, }; const COPY = JSON.stringify({ en: { home: { eyebrow: 'Hello', title: ['Everything', 'in order'], sub: 'Lists and notes.' } } }); const go = async (p: ReturnType, argv: string[]) => { const r = await capture(() => run(p.ctx(argv))); return r; }; describe('shots copy translate', () => { test('without a hook the source is copied in as status=translate; the missing-locale problem names the command', async () => { const p = project(CFG, { 'store/shots/copy.json': COPY }); expect(validateCopy(JSON.parse(COPY), CFG.shots!, ['en', 'de'])[0]).toContain('storebound shots copy translate --to de'); await go(p, ['translate', '--to', 'de']); const copy = JSON.parse(readFileSync(join(p.root, 'store/shots/copy.json'), 'utf8')); expect(copy.de.home.title).toEqual(['Everything', 'in order']); const marks = parseMarks(readFileSync(join(p.root, 'store/shots/copy.translations.json'), 'utf8')); expect(marks.de).toEqual({ from: 'en', status: 'translate' }); expect(marksNotes(marks, 'copy')[0]).toContain('not translated yet'); expect(validateCopy(copy, CFG.shots!, ['en', 'de'])).toEqual([]); }); test('with a hook: draft; an over-limit field keeps the source and the language stays translate; reviewed refuses translate', async () => { const p = project(CFG, { 'store/shots/copy.json': COPY }); await go(p, ['translate', '--to', 'de', '--translate', 'tr a-z A-Z']); let copy = JSON.parse(readFileSync(join(p.root, 'store/shots/copy.json'), 'utf8')); expect(copy.de.home.eyebrow).toBe('HELLO'); expect(parseMarks(readFileSync(join(p.root, 'store/shots/copy.translations.json'), 'utf8')).de!.status).toBe('draft'); const r = await go(p, ['reviewed', '--lang', 'de']); expect(r.exit).toBeFalsy(); expect(readFileSync(join(p.root, 'store/shots/copy.translations.json'), 'utf8').trim()).toBe('{}'); const q = project(CFG, { 'store/shots/copy.json': COPY }); await go(q, ['translate', '--to', 'de', '--translate', 'echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']); copy = JSON.parse(readFileSync(join(q.root, 'store/shots/copy.json'), 'utf8')); expect(copy.de.home.title[0]).toBe('Everything'); expect(parseMarks(readFileSync(join(q.root, 'store/shots/copy.translations.json'), 'utf8')).de!.status).toBe('translate'); }); test('helpers: fields, limits', () => { const sc = JSON.parse(COPY).en.home; expect(sceneFields(sc).map(f => f.key)).toEqual(['eyebrow', 'title.0', 'title.1', 'sub']); expect(applyFields(sc, { sub: 'x' }).sub).toBe('x'); expect(overLimit({ home: { title: ['a'.repeat(40), 'b'] } })[0]).toMatchObject({ field: 'title.0', limit: 28 }); }); });