232 lines
9.2 KiB
TypeScript
232 lines
9.2 KiB
TypeScript
// 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 },
|
||
],
|
||
};
|