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 = >{ '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 targetItemIds; final List 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> dialogueRequiredTerms; /// Terms that show the independent attempt contains the taught content. final List independentRequiredTerms; final int estimatedMinutes; } const a0LessonSegments = >{ '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 vocabulary; final String schemaVersion; final int revision; final String stageVersion; final ContentSource source; final ContentStatus status; final int estimatedMinutes; List get targetItemIds => a0TargetItemIdsByLesson[id] ?? const []; List get abilityIds => ['A0-C${number.toString().padLeft(2, '0')}']; List get prerequisiteIds => number == 1 ? const [] : ['a0-${(number - 1).toString().padLeft(2, '0')}']; List 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. I’m 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: 'I’m good, thanks.', exampleMeaning: '我很好,谢谢。', ipa: '/ɡʊd/', ), VocabularyItem( id: 'a0-03-tired', word: 'tired', meaning: '累的', example: 'I’m 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: '说 0–10 与虚拟号码', 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: 'What’s your phone number?', exampleMeaning: '你的电话号码是多少?', ), ], ), SeedLesson( id: 'a0-05', number: 5, title: '这是什么?', outcome: '询问并说出常见物品', vocabulary: [ VocabularyItem( id: 'a0-05-book', word: 'book', meaning: '书', example: 'It’s a book.', exampleMeaning: '这是一本书。', ipa: '/bʊk/', ), VocabularyItem( id: 'a0-05-pen', word: 'pen', meaning: '笔', example: 'It’s a pen.', exampleMeaning: '这是一支笔。', ipa: '/pen/', ), VocabularyItem( id: 'a0-05-this', word: 'What’s this?', meaning: '这是什么?', example: 'What’s this? It’s a key.', exampleMeaning: '这是什么?这是钥匙。', ), ], ), SeedLesson( id: 'a0-06', number: 6, title: '我来自哪里?', outcome: '说明来自哪里并反问', vocabulary: [ VocabularyItem( id: 'a0-06-from', word: 'from', meaning: '来自', example: 'I’m 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: 'o’clock', meaning: '整点', example: 'It’s three o’clock.', 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 answers; final String speaking; final String reading; final String readingQuestion; final String readingAnswer; final List 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 shuffledOptions(List 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 = { 'a0-01': LessonActivity( listening: 'Hello. I’m Mia. Nice to meet you.', listeningQuestion: 'Mia 在做什么?', answers: ['自我介绍', '买东西', '问时间'], speaking: 'Hello. I’m Shen. Nice to meet you.', reading: 'Mia: Hello. I’m Mia. What’s your name?\nShen: Hi. I’m 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.', 'What’s your name?', 'Hi. I’m Shen.', ], writingPrompt: '写一句问候和姓名介绍。', writingExample: 'Hello. I’m Shen.', independentPrompt: '不看句框,介绍你的名字并回应“Nice to meet you”。', independentHelp: 'Hi. I’m … / Nice to meet you, too.', ), 'a0-02': LessonActivity( listening: 'How do you spell your name?', listeningQuestion: '这句话是什么意思?', answers: ['你的名字怎么拼?', '你的名字是什么?', '你来自哪里?'], speaking: 'S H E N', reading: 'Mia: I’m Mia. M-I-A.\nShen: Hi, Mia. I’m 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? I’m good, thanks.', reading: 'Mia: Hi, Shen. How are you?\nShen: I’m good, thanks. How are you?\nMia: I’m tired today.', readingQuestion: 'Mia 说自己今天怎么样?', readingAnswer: 'I’m tired today.', readingOptions: ['I’m tired today.', 'I’m good, thanks.', 'I’m okay.'], writingPrompt: '写一句今天的状态。', writingExample: 'I’m okay today.', independentPrompt: '不看句框,问候并说出你的状态。', independentHelp: 'How are you? / I’m …', ), 'a0-04': LessonActivity( listening: 'One, three, eight.', listeningQuestion: '你听到的是哪个号码?', answers: ['138', '183', '318'], speaking: 'My number is one-three-eight.', reading: 'Mia: What’s 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: 'What’s this? It’s a key.', listeningQuestion: '这是什么?', answers: ['钥匙', '书', '水'], speaking: 'What’s this? It’s a pen.', reading: 'Mia: What’s this? Is it a pen?\nShen: No. It’s a book.', readingQuestion: 'Shen 说那件东西是什么?', readingAnswer: 'A book', readingOptions: ['A book', 'A pen', 'A phone'], writingPrompt: '写一句“这是一支笔”。', writingExample: 'It’s a pen.', independentPrompt: '不看句框,说出一个身边物品。', independentHelp: 'It’s a …', ), 'a0-06': LessonActivity( listening: 'Where are you from?', listeningQuestion: '对方在问什么?', answers: ['来自哪里', '叫什么', '喜欢什么'], speaking: 'I’m from Hong Kong.', reading: 'Mia: I’m from London. Where are you from?\nShen: I’m from Hong Kong.', readingQuestion: 'Shen 来自哪里?', readingAnswer: 'Hong Kong', readingOptions: ['Hong Kong', 'London', 'Beijing'], writingPrompt: '写一句你来自哪里。', writingExample: 'I’m from Hong Kong.', independentPrompt: '不看句框,说来自哪里并反问对方。', independentHelp: 'I’m 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: 'It’s three o’clock on Monday.', listeningQuestion: '是什么时间?', answers: ['星期一三点', '星期三一点', '星期一一点'], speaking: 'It’s three o’clock.', reading: 'Mia: What day is it today? Is it Tuesday?\nShen: No. It’s Monday.', readingQuestion: '对话中今天到底是星期几?', readingAnswer: 'Monday', readingOptions: ['Monday', 'Tuesday', 'Sunday'], writingPrompt: '写一句今天和整点。', writingExample: 'It’s Monday. It’s three o’clock.', independentPrompt: '不看句框,说今天星期几或现在几点。', independentHelp: 'It’s … / It’s … o’clock.', ), '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 don’t. 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! I’m Alex. What’s your name?\nShen: Hi! I’m Shen. Nice to meet you.\nAlex: Nice to meet you, too. Where are you from?\nShen: I’m from Hong Kong. Where are you from?\nAlex: I’m from London. Do you like coffee?\nShen: No, I don’t. I like tea.', readingQuestion: 'Shen 来自哪里、喜欢喝什么?', readingAnswer: 'Hong Kong, tea', readingOptions: ['Hong Kong, tea', 'London, coffee', 'Hong Kong, coffee'], writingPrompt: '写姓名、地点和喜好三句。', writingExample: 'I’m Shen.\nI’m from Hong Kong.\nI like tea.', independentPrompt: '不看帮助,完成姓名、地点、喜好和反问。', independentHelp: 'I’m … / I’m 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 = { '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: What’s 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 o’clock.', listeningQuestion: '是什么时间?', answers: ['三点', '一点', '星期三'], speaking: 'It is three o’clock.', reading: 'Mia: What time is it? Is it two o’clock?\nShen: No. It is three o’clock.', readingQuestion: 'Shen 说现在是几点?', readingAnswer: 'Three o’clock', readingOptions: ['Three o’clock', 'Two o’clock', 'One o’clock'], writingPrompt: '写一句整点时间。', writingExample: 'It is three o’clock.', independentPrompt: '不看帮助,说一个整点时间。', independentHelp: 'It is … o’clock.', ), }; 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 = >{ '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 vocabularyBySegmentId(String segmentId, String lessonId) { if (!a0SegmentActivities.containsKey(segmentId)) { return lessonById(lessonId).vocabulary; } return a0SegmentVocabulary[segmentId] ?? const []; } /// One practical rule per seed lesson. These are reminders for using a sentence /// in context, not abstract grammar lectures. const a0GrammarNotes = { 'a0-01': '介绍自己时,把名字放在 I’m 后面:I’m Mia. 不需要先学复杂时态。', 'a0-02': 'My name is 后面接名字;拼读时每个字母之间稍微停顿。', 'a0-03': '说状态用 I’m + 状态:I’m good / okay / tired。', 'a0-04': '报号码时数字一个一个说:one-three-eight,不要当成一百三十八。', 'a0-05': '问物品用 What’s this?;回答用 It’s a + 物品。', 'a0-06': '说地点用 I’m from + 地点:I’m from Hong Kong。', 'a0-07': '介绍人用 This is my + 人:This is my mother。', 'a0-08': '星期和整点都可用 It’s:It’s Monday. / It’s three o’clock。', 'a0-09': '说喜好用 I like + 东西;一般问句用 Do you like + 东西?', 'a0-10': '礼貌请求以 Please 开头:Please say that again.', }; String grammarNoteForLesson(String id) => a0GrammarNotes[id] ?? ''; const a0SegmentGrammarNotes = { 'a0-04-a': '数字单独说就可以:zero, one, two。每个词之间稍微停顿。', 'a0-04-b': '认物品可以说 It is a phone. 口语里也常说 It’s a phone.', 'a0-04-c': '报号码时一个数字一个数字说:one-three-eight,不把它读成一百三十八。', 'a0-08-a': '问星期用 What day is it?;回答用 It is / It’s + 星期。', 'a0-08-b': '星期四到星期日也沿用 It is / It’s + 星期这个句型。', 'a0-08-c': '说整点用 It is / It’s + 数字 + o’clock,例如 It’s three o’clock。', }; String grammarNoteForSegment(String segmentId, String lessonId) => a0SegmentGrammarNotes[segmentId] ?? grammarNoteForLesson(lessonId); String _normalizeDialogueInput(String response) => response .toLowerCase() .replaceAll('\u2019', "'") .replaceAll('\u2018', "'") .replaceAll('’', "'") .replaceAll('‘', "'") .replaceAll(RegExp(r'\s+'), ' ') .trim(); bool matchesSegmentDialogue(String segmentId, int stage, String response) { final text = _normalizeDialogueInput(response); 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 terms) => terms.any((term) => text.contains(_normalizeDialogueInput(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 = _normalizeDialogueInput(response); final segment = a0LessonSegments.values .expand((segments) => segments) .where((item) => item.id == segmentId) .firstOrNull; if (segment == null) { // Single-segment lessons: every group of the lesson's independent task // has to be present. Unknown segments never pass. final lessonId = segmentId.replaceFirst(RegExp(r'-[a-z]$'), ''); final groups = a0IndependentRequiredTerms[lessonId]; if (groups == null) return false; return groups.every( (group) => group.any((term) => _containsTerm(text, term)), ); } if (segmentId == 'a0-04-a' || segmentId == 'a0-04-b') { final count = segment.independentRequiredTerms .where((term) => text.contains(_normalizeDialogueInput(term))) .length; return count >= 3; } if (segmentId == 'a0-04-c') { return segment.independentRequiredTerms .where((term) => text.contains(_normalizeDialogueInput(term))) .length >= 3; } return segment.independentRequiredTerms.any( (term) => text.contains(_normalizeDialogueInput(term)), ); } const _introductionTerms = [ "i'm + #word", 'i am + #word', 'my name is', "my name's", ]; /// What the independent attempt of each single-segment lesson has to contain. /// Each inner list is one part of the task; a part passes when any of its /// terms appears. Terms follow the same syntax as [LessonDialogue.requiredTerms]. const a0IndependentRequiredTerms = >>{ // 介绍你的名字并回应 Nice to meet you 'a0-01': [ _introductionTerms, ['nice to meet you'], ], // 介绍名字并拼读它 'a0-02': [ _introductionTerms, ['#spelling'], ], // 问候并说出你的状态 'a0-03': [ ['hello', 'hi', 'hey', 'how are you'], ['good', 'okay', 'ok', 'fine', 'great', 'tired'], ], // 说出一个身边物品 'a0-05': [ ["it's a", 'it is a', "it's an", 'it is an'], ], // 说来自哪里并反问对方 'a0-06': [ ["i'm from", 'i am from'], ['where are you from', '#question'], ], // 介绍一位家人或朋友 'a0-07': [ ['this is my'], ], // 说一个喜好并反问 'a0-09': [ ['i like', 'i love'], ['do you like', '#question'], ], // 完成姓名、地点、喜好和反问 'a0-10': [ _introductionTerms, ["i'm from", 'i am from', 'from + #word'], ['i like', 'i love'], ['#question'], ], }; 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 prompts; final List hints; final List 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> 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 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 normTerm = _normalizeDialogueInput(term); final escaped = RegExp.escape(normTerm); return RegExp('(? switch (token) { // Letters said one by one ("S-H-E-N", "s h e n", "L-I"), or the spelled // name typed as one word ("Shen", "aaa"). Case and hyphens are ignored. '#spelling' => RegExp( r'(?:^|[^a-z])[a-z](?:\s*[-\u2010-\u2015]\s*[a-z]|\s[a-z])+(?![a-z])', ).hasMatch(text) || RegExp(r'^[a-z]{2,}$').hasMatch( text .replaceAll(RegExp(r'[-\u2010-\u2015]'), '') .replaceAll(RegExp(r'^[^a-z]+|[^a-z]+$'), ''), ), // Any spoken or written digit. '#digit' => RegExp( r'(? RegExp(r'[a-z]{2,}').hasMatch(text), // Any question addressed to the partner. '#question' => text.contains('?') || RegExp( r"(? false, }; /// 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: ['介绍姓名', '说明来自哪里', '表达状态或喜好', '反问对方'], ); /// A free practice scene. It opens once [unlockAfterLessonId] is complete /// (always open when null) and only uses language taught up to that lesson. class DialogueScene { const DialogueScene({ required this.id, required this.title, required this.summary, required this.script, required this.recapPrompt, this.unlockAfterLessonId, }); final String id; final String title; final String summary; final LessonDialogue script; /// The follow-up review prompt added after the scene is finished. final String recapPrompt; final String? unlockAfterLessonId; } const a0Scenes = [ DialogueScene( id: 'a0-meet', title: '初次见面', summary: '介绍姓名、地点、状态或喜好,并反问对方', script: a0MeetDialogue, recapPrompt: '再用英语介绍一次自己。', ), DialogueScene( id: 'a0-number', title: '留个电话', summary: '问候、报出号码、再说一遍,并反问对方', unlockAfterLessonId: 'a0-04', recapPrompt: '用英语报一次你的虚拟电话号码。', script: LessonDialogue( goal: '问候并交换一个虚拟电话号码', prompts: [ 'Hi! How are you today?', 'Good. What\u2019s your phone number? Use a fake number.', 'Sorry, please say it again.', 'Thanks! Now ask me one question.', ], hints: [ 'I\u2019m good, thanks.', 'My number is one-three-eight.', 'One-three-eight.', 'What\u2019s your phone number?', ], translations: [ '嗨!你今天好吗?', '好的。你的电话号码是多少?用一个虚拟号码。', '抱歉,请再说一遍。', '谢谢!现在问我一个问题。', ], requiredTerms: [ ['good', 'okay', 'ok', 'fine', 'great', 'tired'], ['#digit'], ['#digit'], ['#question'], ], taskLabels: ['说出你今天的状态', '报出一个号码', '再说一次号码', '反问对方'], ), ), DialogueScene( id: 'a0-photo', title: '看照片聊天', summary: '介绍照片里的人和东西,说喜好,并反问对方', unlockAfterLessonId: 'a0-07', recapPrompt: '用英语介绍一位家人或朋友。', script: LessonDialogue( goal: '介绍照片里的人和物品', prompts: [ 'Nice photo! Who is this?', 'And what\u2019s this in the photo?', 'Where are you from?', 'Cool! Now ask me one question.', ], hints: [ 'This is my sister.', 'It\u2019s a bag.', 'I\u2019m from Hong Kong.', 'Who is this? / What\u2019s this?', ], translations: ['照片真好!这是谁?', '照片里这个是什么?', '你来自哪里?', '真酷!现在问我一个问题。'], requiredTerms: [ ['this is my', 'this is'], ["it's a", 'it is a', "it's an", 'it is an'], ["i'm from", 'i am from', 'from + #word'], ['#question'], ], taskLabels: ['介绍照片里的人', '说出照片里的物品', '说出你来自哪里', '反问对方'], ), ), DialogueScene( id: 'a0-plan', title: '约个时间', summary: '说星期和整点,回答喜好,并反问对方', unlockAfterLessonId: 'a0-09', recapPrompt: '用英语说今天星期几和现在几点。', script: LessonDialogue( goal: '说明星期、时间和喜好', prompts: [ 'Hi! What day is it today?', 'What time is it now?', 'Do you like coffee or tea?', 'Me too! Now ask me one question.', ], hints: [ 'It\u2019s Friday.', 'It\u2019s three o\u2019clock.', 'I like tea.', 'Do you like music?', ], translations: ['嗨!今天星期几?', '现在几点?', '你喜欢咖啡还是茶?', '我也是!现在问我一个问题。'], requiredTerms: [ [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', ], ["o'clock", 'oclock', "it's + #digit", 'it is + #digit'], ['i like', 'i love', 'yes', 'no', "i don't"], ['#question'], ], taskLabels: ['说出今天星期几', '说出现在几点', '说出你的喜好', '反问对方'], ), ), ]; DialogueScene sceneById(String id) => a0Scenes.where((scene) => scene.id == id).firstOrNull ?? a0Scenes.first; const a0Dialogues = { 'a0-01': LessonDialogue( goal: '问候、介绍姓名并回应见面问候', prompts: [ 'Hi! I’m Mia. What’s your name?', 'Nice to meet you. Say: Nice to meet you, too.', 'Great! Say hello one more time.', 'Now ask me my name!', ], hints: [ 'I’m Alex.', 'Nice to meet you, too.', 'Hello!', 'What’s 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! What’s 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.', 'What’s 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?', 'I’m okay. Say your state one more time.', 'Now say goodbye!', ], hints: ['I’m good, thanks.', 'How are you?', 'I’m 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: [ 'What’s 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.', 'What’s 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: [ 'What’s this? It is a pen.', 'Now say: It’s a pen.', 'Ask me: What’s this?', 'Great! Say one more object.', ], hints: ['It’s a pen.', 'It’s a pen.', 'What’s this?', 'It’s a book.'], translations: [ '这是什么?这是一支笔。', '现在请说:It’s a pen(这是一支笔)。', '请问我:What’s 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: ['说出这个物品', '完整说出 It’s a … 句型', '反问 What’s 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: [ 'I’m from Hong Kong.', 'Where are you from?', 'I’m 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: [ 'It’s Monday.', 'It’s three o’clock.', 'It’s three o’clock.', '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.', 'What’s 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 = { '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, two(0,1,2)。', '现在请说 three, four, five(3,4,5)。', ], 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, eight(6,7,8)。', '这是什么?请说: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 o’clock.'], 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 taughtLanguageUpTo(String lessonId) { final result = []; 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 get allTaughtLanguage => a0CoreItems.values.toList();