textmachine/frontend/src/mock/book.ts

232 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The default fixture world, and the rule that governs it: **the default fixture is the HARD
// case.** A book in the middle of the draft wave, where a naive "N of M done" counter reads zero;
// a bank half decided, with a polysemous term arriving as two legal rows; chapters that carry no
// label at all. A comfortable fixture makes the screenshot cycle look at something the product
// never produces.
//
// Shapes are the CONTRACT's, wire-side: this file is what the platform would answer, so the
// narrowing and the screens are exercised on the real form.
//
// ⚠ The prose is a short extract from a real run (蛊真人 zh→ru, off-repo). Taken to check the
// density of the columns and no more — the source is under copyright (question В-2).
import type { components } from '../api/schema';
type Schemas = components['schemas'];
export const bookId = 'bk_guzhenren';
// Mid-draft-wave on purpose: editing has not started, so an end-to-end counter would read 0 for
// hours. 4276 units over 2284 chapters (measured, D39.84).
export const book: Schemas['Book'] = {
id: bookId,
title: 'Гу Чжэньжэнь',
source_lang: 'zh',
target_lang: 'ru',
genre: 'сянься',
chapter_count: 2284,
character_count: 7_780_000,
added_at: '2026-07-28T09:14:00Z',
status: 'translating',
progress: {
draft: { done: 214, total: 4276 },
edit: { done: 0, total: 4276 },
eta_seconds: 9240,
},
note_count: 37,
};
const other = (
id: string,
title: string,
status: Schemas['BookStatus'],
extra: Partial<Schemas['Book']> = {},
): Schemas['Book'] => ({
...book,
id,
title,
status,
chapter_count: 0,
character_count: 0,
progress: { draft: { done: 0, total: 0 }, edit: { done: 0, total: 0 } },
note_count: 0,
...extra,
});
// Every status of the vocabulary on one screen — the only honest way to see that eleven values
// stay distinguishable, and the reason the label is a word and not a coloured dot.
export const books: Schemas['Book'][] = [
book,
other('bk_2', 'Мастер божественных пилюль', 'ready'),
other('bk_3', 'Второе рождение мечника', 'awaiting_bank'),
other('bk_4', 'Хроники северной заставы', 'finalizing'),
other('bk_5', 'Тысяча ступеней', 'parsing'),
other('bk_6', 'Записки о лисах и духах', 'not_started'),
other('bk_7', 'Небесный чиновник', 'paused'),
other('bk_8', 'Сломанный меч', 'stopped'),
// With a machine reason (0.2.3): a rejected book without one is legal too — that is what a
// deployment older than this minor answers — and the intake world carries that case.
other('bk_9', 'Повреждённая выгрузка', 'rejected', { reject_reason: 'source_unreadable' }),
other('bk_10', 'Оборванный свиток', 'failed'),
other('bk_11', 'Свежая загрузка', 'uploading'),
];
export const run: Schemas['Run'] = {
id: 'run_41',
revision: 1841,
status: 'translating',
verify_bank: true,
ceiling_chapters: 400,
paused_reason: null,
started_at: '2026-08-08T06:02:00Z',
finished_at: null,
};
// A chapter has no status — only progress by units. Two of the hard cases live here: a chapter the
// book gives no label to, and a chapter the book does not number. Neither may be papered over with
// a "Chapter N" template; there is no such form (D39.100 K-3).
export const chapters: Schemas['Chapter'][] = [
{ id: 'ch_1', number: 1, heading: 'Нет раскаяния', units_total: 2, units_done: 2, note_count: 0 },
{
id: 'ch_2',
number: 2,
heading: 'Прозрение пятисот лет',
units_total: 4,
units_done: 4,
note_count: 3,
},
{
id: 'ch_3',
number: 3,
heading: 'Церемония открытия',
units_total: 3,
units_done: 1,
note_count: 0,
},
{ id: 'ch_4', number: 4, heading: null, units_total: 2, units_done: 0, note_count: 0 },
{
id: 'ch_5',
number: null,
heading: 'Деревня Гуюэ',
units_total: 1,
units_done: 0,
note_count: 1,
},
{ id: 'ch_6', number: 5, heading: 'Первый гу', units_total: 2, units_done: 0, note_count: 0 },
{ id: 'ch_7', number: 6, heading: 'Аптека', units_total: 2, units_done: 0, note_count: 0 },
{
id: 'ch_8',
number: 7,
heading: 'Кровь на снегу',
units_total: 3,
units_done: 0,
note_count: 0,
},
{
id: 'ch_9',
number: 8,
heading: 'Утраченный свиток',
units_total: 1,
units_done: 0,
note_count: 0,
},
{ id: 'ch_10', number: 9, heading: 'Горная тропа', units_total: 2, units_done: 0, note_count: 0 },
];
export const notes: Schemas['Note'][] = [
{
severity: 'glance',
message: 'Служебная разметка вычищена автоматически',
chapter_id: 'ch_2',
unit_id: 'u_2_1',
},
{
severity: 'attention',
message: 'Часть блока не вошла в перевод',
chapter_id: 'ch_2',
unit_id: 'u_2_2',
},
{
severity: 'attention',
message: 'Блок не переведён: содержимое отклонено',
chapter_id: 'ch_2',
unit_id: 'u_2_3',
},
{ severity: 'glance', message: 'Служебная разметка вычищена автоматически', chapter_id: 'ch_5' },
];
const noteOf = (unitId: string) => notes.find((note) => note.unit_id === unitId) ?? null;
// Source lines for chapters the draft wave has not reached: parsing produced the text, translation
// has not. Deliberately few and short — see the copyright note at the top of the file.
const untouched = ['青茅山下,古月家的少年们排成一列,等待开窍。', '风从北面吹来,带着雪的气味。'];
export function unitsOf(chapterId: string): Schemas['Unit'][] {
const explicit = units[chapterId];
if (explicit) return explicit;
const chapter = chapters.find((row) => row.id === chapterId);
return Array.from({ length: chapter?.units_total ?? 0 }, (_, index) => ({
id: `${chapterId}_u${String(index)}`,
source: untouched[index % untouched.length] ?? '',
target: '',
state: 'pending' as const,
note: null,
}));
}
const units: Record<string, Schemas['Unit'][]> = {
ch_2: [
{
id: 'u_2_0',
source:
'传说中,这个世界存在着一条光阴之河,支撑着这个世界的流转。而利用春秋蝉的力量,就能逆流而上,回到从前。\n' +
'对这个传闻,世人众说纷纭。很多人并不相信,有些人则将信将疑。\n' +
'几乎没有人真的确信。',
target:
'В преданиях говорилось, что в этом мире существует река времени, поддерживающая его течение, и с помощью силы Весенне-осенней цикады можно подняться против потока и вернуться в прошлое.\n' +
'Об этом слухе люди судили по-разному. Многие не верили вовсе, иные верили наполовину.\n' +
'Почти никто не был уверен по-настоящему.',
state: 'translated',
note: null,
},
{
id: 'u_2_1',
source:
'因为每一次使用春秋蝉,都必须付出生命,将整个身躯和所有的修为统统献祭,作为驱动的力量。',
target:
'Ведь каждое обращение к Весенне-осенней цикаде требует жизни: тело и всё накопленное умение приносятся в жертву, чтобы дать ей силу.',
state: 'translated',
note: noteOf('u_2_1'),
},
{
// Text AND a note together — a class, not an exception. A unit flagged for a member that
// dropped out still ships the edited remainder, carrying the reason of the first drop.
id: 'u_2_2',
source:
'但是现在,方源信了,彻底地信了。铁一般的事实摆在他的面前,不容他反驳——他真的重生了!',
target:
'Но теперь Фан Юань уверовал полностью и бесповоротно. Перед ним лежал железный факт, не допускающий возражений: он действительно переродился!',
state: 'translated',
note: noteOf('u_2_2'),
},
{
// Flagged AND no text: the other branch of the same rule.
id: 'u_2_3',
source: '这个代价实在太昂贵了,更让人无法接受的是——往往付出了生命,也不知道结果怎样。',
target: '',
state: 'withheld',
note: noteOf('u_2_3'),
},
],
ch_3: [
{
id: 'u_3_0',
source: '开窍礼在清晨开始,古月家的孩子们站在祠堂前。',
target: 'Церемония открытия началась на рассвете: дети рода Гуюэ стояли перед родовым залом.',
state: 'translated',
note: null,
},
{ id: 'u_3_1', source: untouched[0] ?? '', target: '', state: 'pending', note: null },
{ id: 'u_3_2', source: untouched[1] ?? '', target: '', state: 'pending', note: null },
],
};