Files
English/kouyu_english/lib/core/seed_courses.dart
T
shenleiandClaude Opus 5 7e29c5449f feat: 重做阅读找答案题与 AI 情境对话,并归拢本地识别、查词等既有改动
阅读"在对话里找到答案":
- 16 道题全部重写,干扰项真实出现在对话里,靠说话人归属或否定句才能作答
- 选项按题目内容确定性打乱,答案不再固定排第一;听力环节同样处理
- 去掉超纲干扰项、重复题干,收紧自由作答匹配(过去单个字母也能判对)

AI 情境对话:
- 提示词区分"AI 这一句要做什么"与"学习者随后要完成什么",并下发已教词句清单
- JSON 只强制 reply,translation/feedback 可选;不再索要用不上的 slots/evidence
- AI 不可用时页面明确提示当前回复来自内置示范脚本
- 删掉按 stage 下标猜中文翻译的兜底,避免译文与英文对不上
- 整课对话改用逐轮必需表达校验,替换"关键词沾边就算过";修正自由场景正则误伤
- 总结的"完成任务"按实际通过的轮次生成;模型点评只在结束页呈现一次
- 自由场景支持草稿续练(独立存储槽);修正回答轮数文案与永不解锁的场景标注

同时提交此前工作区中累积的改动:SenseVoice 本地识别、查词/句型解析卡、
复习与测评页调整等,并补充对话校验、选项分布和句子解析的测试。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:38:23 +09:00

1566 lines
51 KiB
Dart
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.
import 'a0_core.dart';
import 'models.dart';
/// Stable A0 core identities taught by each seed lesson. Wording can vary in
/// future exercises without creating a different mastery item.
const a0TargetItemIdsByLesson = <String, List<String>>{
'a0-01': ['A0-W36', 'A0-W37', 'A0-W40', 'A0-P01', 'A0-P02', 'A0-P03'],
'a0-02': ['A0-P04'],
'a0-03': ['A0-W33', 'A0-W34', 'A0-W35', 'A0-P05', 'A0-P06'],
'a0-04': [
'A0-W01',
'A0-W02',
'A0-W03',
'A0-W04',
'A0-W05',
'A0-W06',
'A0-W07',
'A0-W08',
'A0-W09',
'A0-W10',
'A0-W11',
'A0-W20',
'A0-P07',
'A0-P08',
],
'a0-05': ['A0-W19', 'A0-W21', 'A0-W22', 'A0-W23', 'A0-P09', 'A0-P10'],
'a0-06': ['A0-P11', 'A0-P12'],
'a0-07': ['A0-W25', 'A0-W26', 'A0-W27', 'A0-W28', 'A0-P13'],
'a0-08': [
'A0-W12',
'A0-W13',
'A0-W14',
'A0-W15',
'A0-W16',
'A0-W17',
'A0-W18',
'A0-P14',
'A0-P15',
'A0-P16',
'A0-P17',
],
'a0-09': [
'A0-W29',
'A0-W30',
'A0-W31',
'A0-W32',
'A0-W38',
'A0-W39',
'A0-P18',
'A0-P19',
],
'a0-10': ['A0-W24', 'A0-P20'],
};
/// A topic may contain multiple independently resumable teaching segments.
/// Segment IDs are stable even if later wording or exercises change.
class LessonSegment {
const LessonSegment({
required this.id,
required this.targetItemIds,
required this.previewItemIds,
this.dialogueRequiredTerms = const [],
this.independentRequiredTerms = const [],
this.estimatedMinutes = 12,
});
final String id;
final List<String> targetItemIds;
final List<String> previewItemIds;
/// One accepted term group per controlled dialogue turn. A learner only
/// needs to use one term from the current group; the surrounding wording
/// may vary naturally.
final List<List<String>> dialogueRequiredTerms;
/// Terms that show the independent attempt contains the taught content.
final List<String> independentRequiredTerms;
final int estimatedMinutes;
}
const a0LessonSegments = <String, List<LessonSegment>>{
'a0-04': [
LessonSegment(
id: 'a0-04-a',
targetItemIds: [
'A0-W01',
'A0-W02',
'A0-W03',
'A0-W04',
'A0-W05',
'A0-W06',
],
previewItemIds: [
'A0-W01',
'A0-W02',
'A0-W03',
'A0-W04',
'A0-W05',
'A0-W06',
],
dialogueRequiredTerms: [
['zero', 'one', 'two'],
['three', 'four', 'five'],
],
independentRequiredTerms: ['zero', 'one', 'two', 'three', 'four', 'five'],
),
LessonSegment(
id: 'a0-04-b',
targetItemIds: [
'A0-W07',
'A0-W08',
'A0-W09',
'A0-W10',
'A0-W11',
'A0-W20',
],
previewItemIds: [
'A0-W07',
'A0-W08',
'A0-W09',
'A0-W10',
'A0-W11',
'A0-W20',
],
dialogueRequiredTerms: [
['six', 'seven', 'eight'],
['it is a phone', "it's a phone"],
],
independentRequiredTerms: ['six', 'seven', 'eight', 'nine', 'ten'],
),
LessonSegment(
id: 'a0-04-c',
targetItemIds: ['A0-P07', 'A0-P08'],
previewItemIds: ['A0-P07', 'A0-P08'],
dialogueRequiredTerms: [
[
'my number is',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'zero',
],
[
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'zero',
],
],
independentRequiredTerms: [
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'zero',
],
),
],
'a0-08': [
LessonSegment(
id: 'a0-08-a',
targetItemIds: ['A0-W12', 'A0-W13', 'A0-W14', 'A0-P14', 'A0-P15'],
previewItemIds: ['A0-W12', 'A0-W13', 'A0-W14', 'A0-P14', 'A0-P15'],
dialogueRequiredTerms: [
['what day'],
['monday', 'tuesday', 'wednesday'],
],
independentRequiredTerms: ['monday', 'tuesday', 'wednesday'],
),
LessonSegment(
id: 'a0-08-b',
targetItemIds: ['A0-W15', 'A0-W16', 'A0-W17', 'A0-W18'],
previewItemIds: ['A0-W15', 'A0-W16', 'A0-W17', 'A0-W18'],
dialogueRequiredTerms: [
['what day'],
['thursday', 'friday', 'saturday', 'sunday'],
],
independentRequiredTerms: ['thursday', 'friday', 'saturday', 'sunday'],
),
LessonSegment(
id: 'a0-08-c',
targetItemIds: ['A0-P16', 'A0-P17'],
previewItemIds: ['A0-P16', 'A0-P17'],
dialogueRequiredTerms: [
['what time'],
["o'clock", 'oclock'],
],
independentRequiredTerms: ["o'clock", 'oclock'],
),
],
};
class SeedLesson {
const SeedLesson({
required this.id,
required this.number,
required this.title,
required this.outcome,
required this.vocabulary,
this.schemaVersion = '1.0',
this.revision = 1,
this.stageVersion = 'A0-1.0',
this.source = ContentSource.builtInOriginal,
this.status = ContentStatus.approved,
this.estimatedMinutes = 12,
});
final String id;
final int number;
final String title;
final String outcome;
final List<VocabularyItem> vocabulary;
final String schemaVersion;
final int revision;
final String stageVersion;
final ContentSource source;
final ContentStatus status;
final int estimatedMinutes;
List<String> get targetItemIds =>
a0TargetItemIdsByLesson[id] ?? const <String>[];
List<String> get abilityIds => ['A0-C${number.toString().padLeft(2, '0')}'];
List<String> get prerequisiteIds => number == 1
? const []
: ['a0-${(number - 1).toString().padLeft(2, '0')}'];
List<LessonSegment> get segments =>
a0LessonSegments[id] ??
[
LessonSegment(
id: '$id-a',
targetItemIds: targetItemIds,
previewItemIds: targetItemIds,
estimatedMinutes: estimatedMinutes,
),
];
}
const a0SeedLessons = [
SeedLesson(
id: 'a0-01',
number: 1,
title: '你好,我是……',
outcome: '介绍姓名并回应问候',
vocabulary: [
VocabularyItem(
id: 'a0-01-hello',
word: 'Hello',
meaning: '你好',
example: 'Hello. Im Mia.',
exampleMeaning: '你好。我是 Mia。',
ipa: '/həˈləʊ/',
),
VocabularyItem(
id: 'a0-01-name',
word: 'name',
meaning: '名字',
example: 'My name is Shen.',
exampleMeaning: '我的名字是 Shen。',
ipa: '/neɪm/',
),
VocabularyItem(
id: 'a0-01-nice',
word: 'Nice to meet you',
meaning: '很高兴认识你',
example: 'Nice to meet you, too.',
exampleMeaning: '我也很高兴认识你。',
),
],
),
SeedLesson(
id: 'a0-02',
number: 2,
title: '拼读我的名字',
outcome: '听懂拼写,并说出名字字母',
vocabulary: [
VocabularyItem(
id: 'a0-02-spell',
word: 'spell',
meaning: '拼写',
example: 'How do you spell that?',
exampleMeaning: '那个怎么拼?',
ipa: '/spel/',
),
VocabularyItem(
id: 'a0-02-name',
word: 'name',
meaning: '名字',
example: 'My name is Shen.',
exampleMeaning: '我的名字是 Shen。',
ipa: '/neɪm/',
),
VocabularyItem(
id: 'a0-02-question',
word: 'How do you spell that?',
meaning: '那个怎么拼?',
example: 'How do you spell your name?',
exampleMeaning: '你的名字怎么拼?',
),
],
),
SeedLesson(
id: 'a0-03',
number: 3,
title: '你好吗?',
outcome: '问候并表达简单状态',
vocabulary: [
VocabularyItem(
id: 'a0-03-good',
word: 'good',
meaning: '好的',
example: 'Im good, thanks.',
exampleMeaning: '我很好,谢谢。',
ipa: '/ɡʊd/',
),
VocabularyItem(
id: 'a0-03-tired',
word: 'tired',
meaning: '累的',
example: 'Im tired today.',
exampleMeaning: '我今天有点累。',
ipa: '/ˈtaɪəd/',
),
VocabularyItem(
id: 'a0-03-how',
word: 'How are you?',
meaning: '你好吗?',
example: 'How are you today?',
exampleMeaning: '你今天怎么样?',
),
],
),
SeedLesson(
id: 'a0-04',
number: 4,
title: '数字与电话号码',
outcome: '说 010 与虚拟号码',
vocabulary: [
VocabularyItem(
id: 'a0-04-three',
word: 'three',
meaning: '三',
example: 'One, three, eight.',
exampleMeaning: '一、三、八。',
ipa: '/θriː/',
),
VocabularyItem(
id: 'a0-04-number',
word: 'number',
meaning: '号码',
example: 'My number is 1-3-8.',
exampleMeaning: '我的号码是 138。',
ipa: '/ˈnʌmbə(r)/',
),
VocabularyItem(
id: 'a0-04-phone',
word: 'phone',
meaning: '电话',
example: 'Whats your phone number?',
exampleMeaning: '你的电话号码是多少?',
),
],
),
SeedLesson(
id: 'a0-05',
number: 5,
title: '这是什么?',
outcome: '询问并说出常见物品',
vocabulary: [
VocabularyItem(
id: 'a0-05-book',
word: 'book',
meaning: '书',
example: 'Its a book.',
exampleMeaning: '这是一本书。',
ipa: '/bʊk/',
),
VocabularyItem(
id: 'a0-05-pen',
word: 'pen',
meaning: '笔',
example: 'Its a pen.',
exampleMeaning: '这是一支笔。',
ipa: '/pen/',
),
VocabularyItem(
id: 'a0-05-this',
word: 'Whats this?',
meaning: '这是什么?',
example: 'Whats this? Its a key.',
exampleMeaning: '这是什么?这是钥匙。',
),
],
),
SeedLesson(
id: 'a0-06',
number: 6,
title: '我来自哪里?',
outcome: '说明来自哪里并反问',
vocabulary: [
VocabularyItem(
id: 'a0-06-from',
word: 'from',
meaning: '来自',
example: 'Im from Hong Kong.',
exampleMeaning: '我来自香港。',
ipa: '/frəm/',
),
VocabularyItem(
id: 'a0-06-place',
word: 'place',
meaning: '地点',
example: 'Where are you from?',
exampleMeaning: '你来自哪里?',
),
],
),
SeedLesson(
id: 'a0-07',
number: 7,
title: '我的家人',
outcome: '介绍一位家人',
vocabulary: [
VocabularyItem(
id: 'a0-07-family',
word: 'family',
meaning: '家人',
example: 'This is my family.',
exampleMeaning: '这是我的家人。',
ipa: '/ˈfæməli/',
),
VocabularyItem(
id: 'a0-07-mother',
word: 'mother',
meaning: '母亲',
example: 'This is my mother.',
exampleMeaning: '这是我的妈妈。',
),
],
),
SeedLesson(
id: 'a0-08',
number: 8,
title: '星期与时间',
outcome: '说今天和整点',
vocabulary: [
VocabularyItem(
id: 'a0-08-monday',
word: 'Monday',
meaning: '星期一',
example: 'It is Monday.',
exampleMeaning: '今天是星期一。',
),
VocabularyItem(
id: 'a0-08-oclock',
word: 'oclock',
meaning: '整点',
example: 'Its three oclock.',
exampleMeaning: '现在三点整。',
),
],
),
SeedLesson(
id: 'a0-09',
number: 9,
title: '我喜欢……',
outcome: '表达喜好并回答',
vocabulary: [
VocabularyItem(
id: 'a0-09-like',
word: 'like',
meaning: '喜欢',
example: 'I like tea.',
exampleMeaning: '我喜欢茶。',
ipa: '/laɪk/',
),
VocabularyItem(
id: 'a0-09-tea',
word: 'tea',
meaning: '茶',
example: 'Do you like tea?',
exampleMeaning: '你喜欢茶吗?',
),
],
),
SeedLesson(
id: 'a0-10',
number: 10,
title: 'A0 综合任务',
outcome: '完成自我介绍、喜好和反问',
vocabulary: [
VocabularyItem(
id: 'a0-10-again',
word: 'Please say that again.',
meaning: '请再说一遍。',
example: 'Please say that again.',
exampleMeaning: '请再说一遍。',
),
VocabularyItem(
id: 'a0-10-slowly',
word: 'Please speak slowly.',
meaning: '请说慢一点。',
example: 'Please speak slowly.',
exampleMeaning: '请说慢一点。',
),
],
),
];
SeedLesson lessonById(String id) =>
a0SeedLessons.firstWhere((lesson) => lesson.id == id);
class LessonActivity {
const LessonActivity({
required this.listening,
required this.listeningQuestion,
required this.answers,
required this.speaking,
required this.reading,
required this.readingQuestion,
required this.readingAnswer,
this.readingOptions = const [],
required this.writingPrompt,
required this.writingExample,
required this.independentPrompt,
required this.independentHelp,
});
final String listening;
final String listeningQuestion;
final List<String> answers;
final String speaking;
final String reading;
final String readingQuestion;
final String readingAnswer;
final List<String> readingOptions;
final String writingPrompt;
final String writingExample;
final String independentPrompt;
final String independentHelp;
}
int _seedHash(String seed) {
var hash = 0x811c9dc5;
for (final code in seed.codeUnits) {
hash = ((hash ^ code) * 0x01000193) & 0x7fffffff;
}
return hash;
}
/// 选项顺序固定会让学习者养成“永远选第一个”的习惯。[options] 的第一项是标准
/// 答案,这里按题目内容确定性地把它挪到某个位置,并打乱其余干扰项:同一道题
/// 每次进入顺序都一样,但答案在三个位置上分布均匀。
List<String> shuffledOptions(List<String> options, String seed) {
if (options.length < 2) return options;
final distractors = [...options.skip(1)];
var hash = _seedHash(seed);
for (var i = distractors.length - 1; i > 0; i--) {
hash = (hash * 1103515245 + 12345) & 0x7fffffff;
final j = hash % (i + 1);
final swap = distractors[i];
distractors[i] = distractors[j];
distractors[j] = swap;
}
return distractors
..insert(_seedHash('$seed#slot') % options.length, options.first);
}
const a0Activities = <String, LessonActivity>{
'a0-01': LessonActivity(
listening: 'Hello. Im Mia. Nice to meet you.',
listeningQuestion: 'Mia 在做什么?',
answers: ['自我介绍', '买东西', '问时间'],
speaking: 'Hello. Im Shen. Nice to meet you.',
reading:
'Mia: Hello. Im Mia. Whats your name?\nShen: Hi. Im Shen.\nMia: Nice to meet you.\nShen: Nice to meet you, too.',
readingQuestion: 'Shen 说的最后一句是什么?',
readingAnswer: 'Nice to meet you, too.',
readingOptions: [
'Nice to meet you, too.',
'Whats your name?',
'Hi. Im Shen.',
],
writingPrompt: '写一句问候和姓名介绍。',
writingExample: 'Hello. Im Shen.',
independentPrompt: '不看句框,介绍你的名字并回应“Nice to meet you”。',
independentHelp: 'Hi. Im … / Nice to meet you, too.',
),
'a0-02': LessonActivity(
listening: 'How do you spell your name?',
listeningQuestion: '这句话是什么意思?',
answers: ['你的名字怎么拼?', '你的名字是什么?', '你来自哪里?'],
speaking: 'S H E N',
reading:
'Mia: Im Mia. M-I-A.\nShen: Hi, Mia. Im Shen. S-H-E-N.\nMia: How do you spell Sam?\nShen: S-A-M.',
readingQuestion: 'Shen 自己的名字怎么拼写?',
readingAnswer: 'S-H-E-N',
readingOptions: ['S-H-E-N', 'M-I-A', 'S-A-M'],
writingPrompt: '把名字和拼写写完整。',
writingExample: 'My name is Shen.\nS-H-E-N.',
independentPrompt: '不看句框,用英文介绍名字并拼读它。',
independentHelp: 'My name is … / A-B-C.',
),
'a0-03': LessonActivity(
listening: 'How are you today?',
listeningQuestion: '对方在问什么?',
answers: ['你的状态', '你的号码', '你的地点'],
speaking: 'How are you? Im good, thanks.',
reading:
'Mia: Hi, Shen. How are you?\nShen: Im good, thanks. How are you?\nMia: Im tired today.',
readingQuestion: 'Mia 说自己今天怎么样?',
readingAnswer: 'Im tired today.',
readingOptions: ['Im tired today.', 'Im good, thanks.', 'Im okay.'],
writingPrompt: '写一句今天的状态。',
writingExample: 'Im okay today.',
independentPrompt: '不看句框,问候并说出你的状态。',
independentHelp: 'How are you? / Im …',
),
'a0-04': LessonActivity(
listening: 'One, three, eight.',
listeningQuestion: '你听到的是哪个号码?',
answers: ['138', '183', '318'],
speaking: 'My number is one-three-eight.',
reading:
'Mia: Whats your phone number?\nShen: My number is one-three-eight.\nMia: One-eight-three?\nShen: No. One-three-eight.',
readingQuestion: 'Shen 的号码到底是哪一个?',
readingAnswer: '138',
readingOptions: ['138', '183', '318'],
writingPrompt: '把虚拟号码 139 写成英文。',
writingExample: 'one-three-nine',
independentPrompt: '不看句框,说出一个三位虚拟号码。',
independentHelp: 'My number is one-two-three.',
),
'a0-05': LessonActivity(
listening: 'Whats this? Its a key.',
listeningQuestion: '这是什么?',
answers: ['钥匙', '书', '水'],
speaking: 'Whats this? Its a pen.',
reading: 'Mia: Whats this? Is it a pen?\nShen: No. Its a book.',
readingQuestion: 'Shen 说那件东西是什么?',
readingAnswer: 'A book',
readingOptions: ['A book', 'A pen', 'A phone'],
writingPrompt: '写一句“这是一支笔”。',
writingExample: 'Its a pen.',
independentPrompt: '不看句框,说出一个身边物品。',
independentHelp: 'Its a …',
),
'a0-06': LessonActivity(
listening: 'Where are you from?',
listeningQuestion: '对方在问什么?',
answers: ['来自哪里', '叫什么', '喜欢什么'],
speaking: 'Im from Hong Kong.',
reading:
'Mia: Im from London. Where are you from?\nShen: Im from Hong Kong.',
readingQuestion: 'Shen 来自哪里?',
readingAnswer: 'Hong Kong',
readingOptions: ['Hong Kong', 'London', 'Beijing'],
writingPrompt: '写一句你来自哪里。',
writingExample: 'Im from Hong Kong.',
independentPrompt: '不看句框,说来自哪里并反问对方。',
independentHelp: 'Im from … / Where are you from?',
),
'a0-07': LessonActivity(
listening: 'Who is this? This is my mother.',
listeningQuestion: '这个人是谁?',
answers: ['妈妈', '朋友', '老师'],
speaking: 'This is my family.',
reading:
'Mia: Who is this? Is this your father?\nShen: No. This is my mother.',
readingQuestion: 'Shen 介绍的是哪一位家人?',
readingAnswer: 'My mother',
readingOptions: ['My mother', 'My father', 'My sister'],
writingPrompt: '介绍一位家人或朋友。',
writingExample: 'This is my mother.',
independentPrompt: '不看句框,介绍一位家人或朋友。',
independentHelp: 'This is my …',
),
'a0-08': LessonActivity(
listening: 'Its three oclock on Monday.',
listeningQuestion: '是什么时间?',
answers: ['星期一三点', '星期三一点', '星期一一点'],
speaking: 'Its three oclock.',
reading:
'Mia: What day is it today? Is it Tuesday?\nShen: No. Its Monday.',
readingQuestion: '对话中今天到底是星期几?',
readingAnswer: 'Monday',
readingOptions: ['Monday', 'Tuesday', 'Sunday'],
writingPrompt: '写一句今天和整点。',
writingExample: 'Its Monday. Its three oclock.',
independentPrompt: '不看句框,说今天星期几或现在几点。',
independentHelp: 'Its … / Its … oclock.',
),
'a0-09': LessonActivity(
listening: 'Do you like tea? Yes, I do.',
listeningQuestion: '对方喜欢茶吗?',
answers: ['喜欢', '不喜欢', '不知道'],
speaking: 'I like tea. Do you like tea?',
reading:
'Mia: I like tea. Do you like tea?\nShen: No, I dont. I like coffee.',
readingQuestion: 'Shen 喜欢喝什么?',
readingAnswer: 'Coffee',
readingOptions: ['Coffee', 'Tea', 'Music'],
writingPrompt: '写一句你的喜好。',
writingExample: 'I like tea.',
independentPrompt: '不看句框,说一个喜好并反问。',
independentHelp: 'I like … / Do you like …?',
),
'a0-10': LessonActivity(
listening: 'Please say that again.',
listeningQuestion: '对方希望什么?',
answers: ['再说一遍', '说慢一点', '写下来'],
speaking: 'Please speak slowly.',
reading:
'Alex: Hi! Im Alex. Whats your name?\nShen: Hi! Im Shen. Nice to meet you.\nAlex: Nice to meet you, too. Where are you from?\nShen: Im from Hong Kong. Where are you from?\nAlex: Im from London. Do you like coffee?\nShen: No, I dont. I like tea.',
readingQuestion: 'Shen 来自哪里、喜欢喝什么?',
readingAnswer: 'Hong Kong, tea',
readingOptions: ['Hong Kong, tea', 'London, coffee', 'Hong Kong, coffee'],
writingPrompt: '写姓名、地点和喜好三句。',
writingExample: 'Im Shen.\nIm from Hong Kong.\nI like tea.',
independentPrompt: '不看帮助,完成姓名、地点、喜好和反问。',
independentHelp: 'Im … / Im from … / I like …',
),
};
LessonActivity activityByLessonId(String id) => a0Activities[id]!;
/// Reviewed activities for split topics. They intentionally avoid values from
/// later segments so a segment can be completed independently.
const a0SegmentActivities = <String, LessonActivity>{
'a0-04-a': LessonActivity(
listening: 'One, two, three.',
listeningQuestion: '听到哪一组数字?',
answers: ['123', '132', '213'],
speaking: 'Zero, one, two, three.',
reading: 'Mia: One, two, three.\nShen: Four, five.',
readingQuestion: 'Shen 接着说了哪两个数字?',
readingAnswer: 'Four, five',
readingOptions: ['Four, five', 'One, two', 'Three, four'],
writingPrompt: '把 0、1、2、3 写成英文。',
writingExample: 'zero, one, two, three',
independentPrompt: '不看帮助,说出三个 0 到 5 的数字。',
independentHelp: 'zero / one / two / three / four / five',
),
'a0-04-b': LessonActivity(
listening: 'Six, seven, eight.',
listeningQuestion: '听到哪一组数字?',
answers: ['678', '687', '768'],
speaking: 'My phone is here.',
reading: 'Mia: Six, seven, eight.\nShen: Nine, ten.',
readingQuestion: 'Shen 最后说了哪两个数字?',
readingAnswer: 'Nine, ten',
readingOptions: ['Nine, ten', 'Six, seven', 'Seven, eight'],
writingPrompt: '把 6、7、8 写成英文。',
writingExample: 'six, seven, eight',
independentPrompt: '不看帮助,说出三个 6 到 10 的数字。',
independentHelp: 'six / seven / eight / nine / ten',
),
'a0-04-c': LessonActivity(
listening: 'My number is one-three-eight.',
listeningQuestion: '号码是多少?',
answers: ['138', '183', '318'],
speaking: 'My number is one-three-eight.',
reading:
'Mia: Whats your phone number?\nShen: My number is five-zero-two.\nMia: Five-two-zero?\nShen: No. Five-zero-two.',
readingQuestion: 'Shen 最后确认的号码是哪一个?',
readingAnswer: '502',
readingOptions: ['502', '520', '205'],
writingPrompt: '把虚拟号码 139 写成英文。',
writingExample: 'one-three-nine',
independentPrompt: '不看句框,说出一个三位虚拟号码。',
independentHelp: 'My number is one-two-three.',
),
'a0-08-a': LessonActivity(
listening: 'It is Monday.',
listeningQuestion: '今天星期几?',
answers: ['星期一', '星期二', '星期三'],
speaking: 'It is Monday.',
reading:
'Mia: What day is it today? Is it Monday?\nShen: No. It is Tuesday.',
readingQuestion: 'Mia 猜错了,今天其实是星期几?',
readingAnswer: 'Tuesday',
readingOptions: ['Tuesday', 'Monday', 'Wednesday'],
writingPrompt: '写一句今天是星期一、二或三。',
writingExample: 'It is Monday.',
independentPrompt: '不看帮助,说出星期一、二或三。',
independentHelp: 'It is Monday / Tuesday / Wednesday.',
),
'a0-08-b': LessonActivity(
listening: 'It is Friday.',
listeningQuestion: '今天星期几?',
answers: ['星期五', '星期四', '星期日'],
speaking: 'It is Friday.',
reading: 'Mia: Is it Saturday today?\nShen: No. It is Sunday.',
readingQuestion: 'Shen 说今天是哪一天?',
readingAnswer: 'Sunday',
readingOptions: ['Sunday', 'Saturday', 'Friday'],
writingPrompt: '写一句今天是星期四到日。',
writingExample: 'It is Friday.',
independentPrompt: '不看帮助,说出星期四到日。',
independentHelp: 'It is Thursday / Friday / Saturday / Sunday.',
),
'a0-08-c': LessonActivity(
listening: 'It is three oclock.',
listeningQuestion: '是什么时间?',
answers: ['三点', '一点', '星期三'],
speaking: 'It is three oclock.',
reading:
'Mia: What time is it? Is it two oclock?\nShen: No. It is three oclock.',
readingQuestion: 'Shen 说现在是几点?',
readingAnswer: 'Three oclock',
readingOptions: ['Three oclock', 'Two oclock', 'One oclock'],
writingPrompt: '写一句整点时间。',
writingExample: 'It is three oclock.',
independentPrompt: '不看帮助,说一个整点时间。',
independentHelp: 'It is … oclock.',
),
};
LessonActivity activityBySegmentId(String segmentId, String lessonId) =>
a0SegmentActivities[segmentId] ?? activityByLessonId(lessonId);
/// Short, reviewed entries used before each split segment. These are kept
/// separate from review prompts: a review hint is not a learner-facing
/// dictionary definition.
const a0SegmentVocabulary = <String, List<VocabularyItem>>{
'a0-04-a': [
VocabularyItem(
id: 'A0-W01',
word: 'zero',
meaning: '零',
example: 'Zero, one, two.',
exampleMeaning: '零、一、二。',
ipa: '/ˈzɪrəʊ/',
),
VocabularyItem(
id: 'A0-W02',
word: 'one',
meaning: '一',
example: 'One, two, three.',
exampleMeaning: '一、二、三。',
ipa: '/wʌn/',
),
VocabularyItem(
id: 'A0-W03',
word: 'two',
meaning: '二',
example: 'Two and three.',
exampleMeaning: '二和三。',
ipa: '/tuː/',
),
VocabularyItem(
id: 'A0-W04',
word: 'three',
meaning: '三',
example: 'Three, four, five.',
exampleMeaning: '三、四、五。',
ipa: '/θriː/',
),
VocabularyItem(
id: 'A0-W05',
word: 'four',
meaning: '四',
example: 'Four and five.',
exampleMeaning: '四和五。',
ipa: '/fɔː/',
),
VocabularyItem(
id: 'A0-W06',
word: 'five',
meaning: '五',
example: 'Five, please.',
exampleMeaning: '五,请。',
ipa: '/faɪv/',
),
],
'a0-04-b': [
VocabularyItem(
id: 'A0-W07',
word: 'six',
meaning: '六',
example: 'Six, seven, eight.',
exampleMeaning: '六、七、八。',
ipa: '/sɪks/',
),
VocabularyItem(
id: 'A0-W08',
word: 'seven',
meaning: '七',
example: 'Seven, eight, nine.',
exampleMeaning: '七、八、九。',
ipa: '/ˈsevən/',
),
VocabularyItem(
id: 'A0-W09',
word: 'eight',
meaning: '八',
example: 'Eight, nine, ten.',
exampleMeaning: '八、九、十。',
ipa: '/eɪt/',
),
VocabularyItem(
id: 'A0-W10',
word: 'nine',
meaning: '九',
example: 'Nine and ten.',
exampleMeaning: '九和十。',
ipa: '/naɪn/',
),
VocabularyItem(
id: 'A0-W11',
word: 'ten',
meaning: '十',
example: 'Ten, please.',
exampleMeaning: '十,请。',
ipa: '/ten/',
),
VocabularyItem(
id: 'A0-W20',
word: 'phone',
meaning: '电话;手机',
example: 'It is a phone.',
exampleMeaning: '它是一部手机。',
ipa: '/fəʊn/',
),
],
'a0-04-c': [
VocabularyItem(
id: 'A0-P07',
word: "What's your phone number?",
meaning: '你的电话号码是多少?',
example: "What's your phone number?",
exampleMeaning: '你的电话号码是多少?',
),
VocabularyItem(
id: 'A0-P08',
word: 'My number is one-three-eight.',
meaning: '我的号码是 138。',
example: 'My number is one-three-eight.',
exampleMeaning: '我的号码是 138。',
),
],
'a0-08-a': [
VocabularyItem(
id: 'A0-W12',
word: 'Monday',
meaning: '星期一',
example: 'It is Monday.',
exampleMeaning: '今天是星期一。',
ipa: '/ˈmʌndeɪ/',
),
VocabularyItem(
id: 'A0-W13',
word: 'Tuesday',
meaning: '星期二',
example: 'It is Tuesday.',
exampleMeaning: '今天是星期二。',
ipa: '/ˈtjuːzdeɪ/',
),
VocabularyItem(
id: 'A0-W14',
word: 'Wednesday',
meaning: '星期三',
example: 'It is Wednesday.',
exampleMeaning: '今天是星期三。',
ipa: '/ˈwenzdeɪ/',
),
VocabularyItem(
id: 'A0-P14',
word: 'What day is it today?',
meaning: '今天星期几?',
example: 'What day is it today?',
exampleMeaning: '今天星期几?',
),
VocabularyItem(
id: 'A0-P15',
word: "It's Monday.",
meaning: '今天是星期一。',
example: "It's Monday.",
exampleMeaning: '今天是星期一。',
),
],
'a0-08-b': [
VocabularyItem(
id: 'A0-W15',
word: 'Thursday',
meaning: '星期四',
example: 'It is Thursday.',
exampleMeaning: '今天是星期四。',
ipa: '/ˈθɜːzdeɪ/',
),
VocabularyItem(
id: 'A0-W16',
word: 'Friday',
meaning: '星期五',
example: 'It is Friday.',
exampleMeaning: '今天是星期五。',
ipa: '/ˈfraɪdeɪ/',
),
VocabularyItem(
id: 'A0-W17',
word: 'Saturday',
meaning: '星期六',
example: 'It is Saturday.',
exampleMeaning: '今天是星期六。',
ipa: '/ˈsætədeɪ/',
),
VocabularyItem(
id: 'A0-W18',
word: 'Sunday',
meaning: '星期日',
example: 'It is Sunday.',
exampleMeaning: '今天是星期日。',
ipa: '/ˈsʌndeɪ/',
),
],
'a0-08-c': [
VocabularyItem(
id: 'A0-P16',
word: 'What time is it?',
meaning: '现在几点?',
example: 'What time is it?',
exampleMeaning: '现在几点?',
),
VocabularyItem(
id: 'A0-P17',
word: "It's three o'clock.",
meaning: '现在三点。',
example: "It's three o'clock.",
exampleMeaning: '现在三点。',
),
],
};
List<VocabularyItem> vocabularyBySegmentId(String segmentId, String lessonId) {
if (!a0SegmentActivities.containsKey(segmentId)) {
return lessonById(lessonId).vocabulary;
}
return a0SegmentVocabulary[segmentId] ?? const <VocabularyItem>[];
}
/// One practical rule per seed lesson. These are reminders for using a sentence
/// in context, not abstract grammar lectures.
const a0GrammarNotes = <String, String>{
'a0-01': '介绍自己时,把名字放在 I’m 后面:I’m Mia. 不需要先学复杂时态。',
'a0-02': 'My name is 后面接名字;拼读时每个字母之间稍微停顿。',
'a0-03': '说状态用 Im + 状态:Im good / okay / tired。',
'a0-04': '报号码时数字一个一个说:one-three-eight,不要当成一百三十八。',
'a0-05': '问物品用 Whats this?;回答用 Its a + 物品。',
'a0-06': '说地点用 Im from + 地点:Im from Hong Kong。',
'a0-07': '介绍人用 This is my + 人:This is my mother。',
'a0-08': '星期和整点都可用 ItsIts Monday. / Its three oclock。',
'a0-09': '说喜好用 I like + 东西;一般问句用 Do you like + 东西?',
'a0-10': '礼貌请求以 Please 开头:Please say that again.',
};
String grammarNoteForLesson(String id) => a0GrammarNotes[id] ?? '';
const a0SegmentGrammarNotes = <String, String>{
'a0-04-a': '数字单独说就可以:zero, one, two。每个词之间稍微停顿。',
'a0-04-b': '认物品可以说 It is a phone. 口语里也常说 Its a phone.',
'a0-04-c': '报号码时一个数字一个数字说:one-three-eight,不把它读成一百三十八。',
'a0-08-a': '问星期用 What day is it?;回答用 It is / Its + 星期。',
'a0-08-b': '星期四到星期日也沿用 It is / It’s + 星期这个句型。',
'a0-08-c': '说整点用 It is / Its + 数字 + oclock,例如 Its three oclock。',
};
String grammarNoteForSegment(String segmentId, String lessonId) =>
a0SegmentGrammarNotes[segmentId] ?? grammarNoteForLesson(lessonId);
bool matchesSegmentDialogue(String segmentId, int stage, String response) {
final text = response.toLowerCase().replaceAll('', "'");
final segment = a0LessonSegments.values
.expand((segments) => segments)
.where((item) => item.id == segmentId)
.firstOrNull;
if (segment == null || stage >= segment.dialogueRequiredTerms.length) {
return false;
}
bool hasAny(Iterable<String> terms) =>
terms.any((term) => text.contains(term));
// A phone-number report needs both the reporting frame and at least one
// spoken digit. Other segment turns need one reviewed, task-specific term.
if (segmentId == 'a0-04-c' && stage == 0) {
return text.contains('my number is') &&
hasAny(segment.dialogueRequiredTerms[stage].skip(1));
}
return hasAny(segment.dialogueRequiredTerms[stage]);
}
bool matchesSegmentIndependent(String segmentId, String response) {
final text = response.toLowerCase().replaceAll('', "'");
final segment = a0LessonSegments.values
.expand((segments) => segments)
.where((item) => item.id == segmentId)
.firstOrNull;
if (segment == null) return true;
if (segmentId == 'a0-04-a' || segmentId == 'a0-04-b') {
final count = segment.independentRequiredTerms
.where((term) => text.contains(term))
.length;
return count >= 3;
}
if (segmentId == 'a0-04-c') {
return segment.independentRequiredTerms
.where((term) => text.contains(term))
.length >=
3;
}
return segment.independentRequiredTerms.any((term) => text.contains(term));
}
class LessonDialogue {
const LessonDialogue({
required this.goal,
required this.prompts,
required this.hints,
this.translations = const [],
this.requiredTerms = const [],
this.taskLabels = const [],
});
final String goal;
final List<String> prompts;
final List<String> hints;
final List<String> translations;
/// One accepted term group per learner turn. The learner only needs to hit
/// one term in the current group, so natural wording still passes. A term
/// may be a plain word or phrase, an `a + b` conjunction (both parts are
/// required), or a structural token starting with `#`.
final List<List<String>> requiredTerms;
/// Chinese label of what each learner turn has to do. It drives the
/// "还没完成本轮任务" message and the summary of completed tasks, so it must
/// describe the task without giving away the model answer.
final List<String> taskLabels;
}
/// Matches whole words only: the old `text.contains('it')` accepted almost any
/// sentence, including ones that never performed the task.
bool _containsTerm(String text, String term) {
if (term.contains(' + ')) {
return term.split(' + ').every((part) => _containsTerm(text, part.trim()));
}
if (term.startsWith('#')) return _matchesStructure(text, term);
final escaped = RegExp.escape(term.toLowerCase());
return RegExp('(?<![a-z])$escaped(?![a-z])').hasMatch(text);
}
bool _matchesStructure(String text, String token) => switch (token) {
// Three or more letters said one by one, e.g. "S-H-E-N" or "s h e n".
'#spelling' => RegExp(r'(?:^|[^a-z])[a-z](?:[ -][a-z]){2,}').hasMatch(text),
// Any spoken or written digit.
'#digit' => RegExp(
r'(?<![a-z])(zero|one|two|three|four|five|six|seven|eight|nine|ten)(?![a-z])|[0-9]',
).hasMatch(text),
// A real word after the frame, so "I'm" alone is not a name.
'#word' => RegExp(r'[a-z]{2,}').hasMatch(text),
// Any question addressed to the partner.
'#question' =>
text.contains('?') ||
RegExp(
r"(?<![a-z])(what|where|who|when|how|why|do you|are you|can you)(?![a-z])",
).hasMatch(text),
_ => false,
};
String _normalizeDialogueInput(String response) =>
response.toLowerCase().replaceAll('\u2019', "'").replaceAll('\u2018', "'");
/// Whole-lesson and free-scene dialogues validate the same way segment
/// dialogues do: the turn has to contain the language the turn is teaching.
bool matchesDialogueStage(LessonDialogue script, int stage, String response) {
final text = _normalizeDialogueInput(response);
if (stage < 0 || stage >= script.requiredTerms.length) {
// No declared requirement: accept anything that is actually a word.
return RegExp(r'[a-z]{2,}').hasMatch(text);
}
final terms = script.requiredTerms[stage];
if (terms.isEmpty) return RegExp(r'[a-z]{2,}').hasMatch(text);
return terms.any((term) => _containsTerm(text, term));
}
String dialogueTaskLabel(LessonDialogue script, int stage) =>
stage >= 0 && stage < script.taskLabels.length
? script.taskLabels[stage]
: '完成本轮任务';
/// The free "初次见面" scene. It lives next to the lesson dialogues so its
/// tasks are validated and summarised by the same rules.
const a0MeetDialogue = LessonDialogue(
goal: '姓名、地点、状态或喜好,并反问',
prompts: [
'Hi! My name is Mia. What\u2019s your name?',
'Nice to meet you. Where are you from?',
'Great! How are you today? Or what do you like?',
'I\u2019m good, thanks. Now ask me one question!',
],
hints: [
'My name is Alex.',
'I\u2019m from Hong Kong.',
'I\u2019m good, thanks. / I like coffee.',
'What\u2019s your name? / Where are you from?',
],
translations: [
'嗨!我叫 Mia。你叫什么名字?',
'很高兴认识你。你来自哪里?',
'很好!你今天怎么样?或者你喜欢什么?',
'我很好,谢谢。现在请问我一个问题!',
],
requiredTerms: [
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
["i'm from", 'i am from', 'from + #word'],
[
'good',
'okay',
'ok',
'fine',
'great',
'tired',
'i like',
'i love',
],
['#question'],
],
taskLabels: ['介绍姓名', '说明来自哪里', '表达状态或喜好', '反问对方'],
);
const a0Dialogues = <String, LessonDialogue>{
'a0-01': LessonDialogue(
goal: '问候、介绍姓名并回应见面问候',
prompts: [
'Hi! Im Mia. Whats your name?',
'Nice to meet you. Say: Nice to meet you, too.',
'Great! Say hello one more time.',
'Now ask me my name!',
],
hints: [
'Im Alex.',
'Nice to meet you, too.',
'Hello!',
'Whats your name?',
],
translations: [
'嗨!我是 Mia。你叫什么名字?',
'很高兴认识你。请说:Nice to meet you, too(我也很高兴认识你)。',
'太棒了!再打一次招呼吧。',
'现在请问我的名字!',
],
requiredTerms: [
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
['nice to meet you'],
['hello', 'hi', 'hey'],
["what's your name", 'what is your name', 'your name', '#question'],
],
taskLabels: ['说出你的名字', '回应 Nice to meet you', '再打一次招呼', '反问对方的名字'],
),
'a0-02': LessonDialogue(
goal: '介绍姓名并完整拼读名字',
prompts: [
'Hi! Whats your name?',
'How do you spell that?',
'Thank you. Spell it one more time.',
'Now ask me my name!',
],
hints: ['My name is Alex.', 'A-L-E-X.', 'A-L-E-X.', 'Whats your name?'],
translations: ['嗨!你叫什么名字?', '那个怎么拼写?', '谢谢。请再拼写一次。', '现在请问我的名字!'],
requiredTerms: [
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
['#spelling'],
['#spelling'],
["what's your name", 'what is your name', 'your name', '#question'],
],
taskLabels: ['说出你的名字', '拼读你的名字', '再拼读一次', '反问对方的名字'],
),
'a0-03': LessonDialogue(
goal: '询问状态、回答并反问',
prompts: [
'Hi! How are you today?',
'Good! Ask me: How are you?',
'Im okay. Say your state one more time.',
'Now say goodbye!',
],
hints: ['Im good, thanks.', 'How are you?', 'Im okay.', 'Bye!'],
translations: [
'嗨!你今天好吗?',
'很好!请问我:How are you(你好吗)?',
'我还好。请再说一次你的状态。',
'现在请说再见!',
],
requiredTerms: [
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
['how are you', '#question'],
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
['bye', 'goodbye', 'see you'],
],
taskLabels: ['说出你今天的状态', '反问对方的状态', '再说一次你的状态', '说再见'],
),
'a0-04': LessonDialogue(
goal: '报告一个虚拟三位号码并确认',
prompts: [
'Whats your phone number? Use a fake three-digit number.',
'I heard one-three-eight. Is that right?',
'Please say the three numbers again.',
'Now ask me for my number!',
],
hints: [
'My number is one-three-eight.',
'Yes.',
'One-three-eight.',
'Whats your phone number?',
],
translations: [
'你的电话号码是多少?可以使用一个虚拟的三位数字。',
'我听到了 1-3-8。对吗?',
'请再说一遍这三个数字。',
'现在请问我的电话号码!',
],
requiredTerms: [
['my number is + #digit', "my number's + #digit", 'number is + #digit'],
['yes', 'no', "that's right", 'right', 'correct'],
['#digit'],
["what's your phone number", 'what is your phone number', 'your phone number', 'your number'],
],
taskLabels: ['报出一个三位号码', '确认或纠正听到的号码', '再说一次这三个数字', '反问对方的号码'],
),
'a0-05': LessonDialogue(
goal: '询问并说出一个物品',
prompts: [
'Whats this? It is a pen.',
'Now say: Its a pen.',
'Ask me: Whats this?',
'Great! Say one more object.',
],
hints: ['Its a pen.', 'Its a pen.', 'Whats this?', 'Its a book.'],
translations: [
'这是什么?这是一支笔。',
'现在请说:Its a pen(这是一支笔)。',
'请问我:Whats this(这是什么)?',
'太棒了!再说一个物品吧。',
],
requiredTerms: [
["it's a", 'it is a', "it's an", 'it is an'],
["it's a", 'it is a', "it's an", 'it is an'],
["what's this", 'what is this'],
["it's a", 'it is a', "it's an", 'it is an'],
],
taskLabels: ['说出这个物品', '完整说出 Its a … 句型', '反问 Whats this', '再说一个物品'],
),
'a0-06': LessonDialogue(
goal: '说来自哪里并反问',
prompts: [
'Where are you from?',
'Nice! Ask me: Where are you from?',
'Say where you are from one more time.',
'Say goodbye!',
],
hints: [
'Im from Hong Kong.',
'Where are you from?',
'Im from Hong Kong.',
'Bye!',
],
translations: [
'你来自哪里?',
'很好!请问我:Where are you from(你来自哪里)?',
'请再说一次你来自哪里。',
'请说再见!',
],
requiredTerms: [
["i'm from", 'i am from', 'from + #word'],
['where are you from', "where're you from", 'where are you'],
["i'm from", 'i am from', 'from + #word'],
['bye', 'goodbye', 'see you'],
],
taskLabels: ['说出你来自哪里', '反问对方来自哪里', '再说一次你来自哪里', '说再见'],
),
'a0-07': LessonDialogue(
goal: '介绍一位家人或朋友',
prompts: [
'Who is this?',
'Good. Say: This is my mother.',
'Ask me: Who is this?',
'Introduce one person again.',
],
hints: [
'This is my mother.',
'This is my mother.',
'Who is this?',
'This is my friend.',
],
translations: [
'这是谁?',
'很好。请说:This is my mother(这是我的妈妈)。',
'请问我:Who is this(这是谁)?',
'请再介绍一个人。',
],
requiredTerms: [
['this is my', 'this is'],
['this is my', 'this is'],
['who is this', "who's this"],
['this is my', 'this is'],
],
taskLabels: ['介绍一位家人或朋友', '完整说出 This is my … 句型', '反问 Who is this', '再介绍一个人'],
),
'a0-08': LessonDialogue(
goal: '说明星期或整点',
prompts: [
'What day is it today?',
'What time is it?',
'Say one full time sentence.',
'Now ask me the day!',
],
hints: [
'Its Monday.',
'Its three oclock.',
'Its three oclock.',
'What day is it?',
],
translations: ['今天星期几?', '现在几点了?', '请说一个完整的时间句子。', '现在请问我今天是星期几!'],
requiredTerms: [
['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
["o'clock", 'oclock', "#digit + o'clock"],
["it's + o'clock", "it is + o'clock", "it's + #digit", 'it is + #digit'],
['what day is it', 'what day', '#question'],
],
taskLabels: ['说出今天星期几', '说出现在几点', '说一个完整的时间句子', '反问今天星期几'],
),
'a0-09': LessonDialogue(
goal: '表达喜好、回答和反问',
prompts: [
'What do you like?',
'Do you like tea?',
'Now ask me what I like.',
'Say your like one more time.',
],
hints: ['I like tea.', 'Yes, I do.', 'Do you like tea?', 'I like tea.'],
translations: ['你喜欢什么?', '你喜欢茶吗?', '现在请问我喜欢什么。', '请再说一次你的喜好。'],
requiredTerms: [
['i like', 'i love'],
['yes', 'no', 'i do', "i don't", 'i do not'],
['do you like', '#question'],
['i like', 'i love'],
],
taskLabels: ['说出你喜欢什么', '回答是否喜欢', '反问对方的喜好', '再说一次你的喜好'],
),
'a0-10': LessonDialogue(
goal: '请求重复或放慢语速,并完成基础沟通',
prompts: [
'Say: Please say that again.',
'Say: Please speak slowly.',
'Now ask me your name or where I am from.',
'Say one thing you like.',
],
hints: [
'Please say that again.',
'Please speak slowly.',
'Whats your name?',
'I like tea.',
],
translations: [
'请说:Please say that again(请再说一遍)。',
'请说:Please speak slowly(请说慢一点)。',
'现在请问我的名字或我来自哪里。',
'请说一件你喜欢的事物。',
],
requiredTerms: [
['say that again', 'again', 'pardon'],
['slowly', 'slow'],
["what's your name", 'what is your name', 'where are you from', 'your name', '#question'],
['i like', 'i love'],
],
taskLabels: ['请求对方重复', '请求对方放慢语速', '反问名字或来自哪里', '说出一件你喜欢的事物'],
),
};
const a0SegmentDialogues = <String, LessonDialogue>{
'a0-04-a': LessonDialogue(
goal: '听辨并说出 0 到 5',
prompts: ['Say zero, one, two.', 'Now say three, four, five.'],
hints: ['zero, one, two', 'three, four, five'],
translations: [
'请说 zero, one, two012)。',
'现在请说 three, four, five345)。',
],
taskLabels: ['说出 zero, one, two', '说出 three, four, five'],
),
'a0-04-b': LessonDialogue(
goal: '听辨 6 到 10 并认识 phone',
prompts: ['Say six, seven, eight.', 'What is this? Say: It is a phone.'],
hints: ['six, seven, eight', 'It is a phone.'],
translations: [
'请说 six, seven, eight678)。',
'这是什么?请说:It is a phone(这是一部手机)。',
],
taskLabels: ['说出 six, seven, eight', '说出 It is a phone.'],
),
'a0-04-c': LessonDialogue(
goal: '询问并报告三位号码',
prompts: ['What is your phone number?', 'Say a three-digit number again.'],
hints: ['My number is one-three-eight.', 'one-three-eight'],
translations: ['你的电话号码是多少?', '请再说一次三位数字。'],
taskLabels: ['报出你的三位号码', '再说一次三位数字'],
),
'a0-08-a': LessonDialogue(
goal: '询问并说出星期一到三',
prompts: ['What day is it?', 'Say Monday, Tuesday, or Wednesday.'],
hints: ['What day is it?', 'It is Monday.'],
translations: ['今天星期几?', '请说 Monday, Tuesday, 或 Wednesday(周一、周二或周三)。'],
taskLabels: ['问今天星期几', '说出周一到周三中的一天'],
),
'a0-08-b': LessonDialogue(
goal: '说出星期四到日',
prompts: ['What day is it?', 'Say Thursday, Friday, Saturday, or Sunday.'],
hints: ['What day is it?', 'It is Friday.'],
translations: [
'今天星期几?',
'请说 Thursday, Friday, Saturday, 或 Sunday(周四、周五、周六或周日)。',
],
taskLabels: ['问今天星期几', '说出周四到周日中的一天'],
),
'a0-08-c': LessonDialogue(
goal: '询问并说出整点',
prompts: ['What time is it?', 'Say one full time sentence.'],
hints: ['What time is it?', 'It is three oclock.'],
translations: ['现在几点了?', '请说一个完整的时间句子。'],
taskLabels: ['问现在几点', '说一个完整的时间句子'],
),
};
LessonDialogue dialogueByLessonId(String id) => a0Dialogues[id]!;
LessonDialogue dialogueBySegmentId(String segmentId, String lessonId) =>
a0SegmentDialogues[segmentId] ?? dialogueByLessonId(lessonId);
/// The English a learner has actually met up to and including [lessonId].
/// The AI partner is told to stay inside this list so a beginner never gets an
/// answer built from words the course has not taught yet.
List<String> taughtLanguageUpTo(String lessonId) {
final result = <String>[];
for (final entry in a0TargetItemIdsByLesson.entries) {
for (final id in entry.value) {
final word = a0CoreItems[id];
if (word != null) result.add(word);
}
if (entry.key == lessonId) break;
}
return result;
}
/// Everything A0 teaches, for the free scene that mixes all topics.
List<String> get allTaughtLanguage => a0CoreItems.values.toList();