diff --git a/shared/config/curriculum.ts b/shared/config/curriculum.ts index 34d1bf34b2f..d340b472741 100644 --- a/shared/config/curriculum.ts +++ b/shared/config/curriculum.ts @@ -53,10 +53,22 @@ export const languageSuperBlocks = [ SuperBlocks.A2Chinese ]; +export enum ChallengeLang { + English = 'en-US', + Spanish = 'es', + Chinese = 'zh-CN' +} + // Mapping from superblock to a speech recognition language (BCP-47) -export const superBlockToSpeechLang: Partial> = { - [SuperBlocks.A2English]: 'en-US', - [SuperBlocks.B1English]: 'en-US' +export const superBlockToSpeechLang: Partial< + Record +> = { + [SuperBlocks.A1Chinese]: ChallengeLang.Chinese, + [SuperBlocks.A2Chinese]: ChallengeLang.Chinese, + [SuperBlocks.A2English]: ChallengeLang.English, + [SuperBlocks.B1English]: ChallengeLang.English, + [SuperBlocks.A1Spanish]: ChallengeLang.Spanish, + [SuperBlocks.A2Spanish]: ChallengeLang.Spanish }; /* diff --git a/tools/challenge-helper-scripts/create-language-block.ts b/tools/challenge-helper-scripts/create-language-block.ts index 92a3476fcd2..21ea3358248 100644 --- a/tools/challenge-helper-scripts/create-language-block.ts +++ b/tools/challenge-helper-scripts/create-language-block.ts @@ -30,6 +30,7 @@ import { updateSimpleSuperblockStructure, updateChapterModuleSuperblockStructure } from './helpers/create-project'; +import { getLangFromSuperBlock } from './helpers/get-lang-from-superblock'; const helpCategories = [ 'English', @@ -78,13 +79,23 @@ async function createLanguageBlock( } await updateIntroJson(superBlock, block, title); + const challengeLang = getLangFromSuperBlock(superBlock); let challengeId: ObjectID; if (blockLabel === BlockLabel.quiz) { - challengeId = await createQuizChallenge(block, title, questionCount!); + challengeId = await createQuizChallenge( + block, + title, + questionCount!, + challengeLang + ); blockLayout = BlockLayouts.Link; } else { - challengeId = await createDialogueChallenge(superBlock, block); + challengeId = await createDialogueChallenge( + superBlock, + block, + challengeLang + ); } await createMetaJson( @@ -178,7 +189,8 @@ async function createMetaJson( async function createDialogueChallenge( superBlock: SuperBlocks, - block: string + block: string, + challengeLang: string ): Promise { const { blockContentDir } = getContentConfig('english') as { blockContentDir: string; @@ -188,14 +200,16 @@ async function createDialogueChallenge( await fs.mkdir(newChallengeDir, { recursive: true }); return createDialogueFile({ - projectPath: newChallengeDir + '/' + projectPath: newChallengeDir + '/', + challengeLang: challengeLang }); } async function createQuizChallenge( block: string, title: string, - questionCount: number + questionCount: number, + challengeLang: string ): Promise { const newChallengeDir = path.resolve( __dirname, @@ -208,7 +222,8 @@ async function createQuizChallenge( projectPath: newChallengeDir + '/', title: title, dashedName: block, - questionCount: questionCount + questionCount: questionCount, + challengeLang }); } diff --git a/tools/challenge-helper-scripts/create-next-task.ts b/tools/challenge-helper-scripts/create-next-task.ts index a7361094fe5..40db674b0e1 100644 --- a/tools/challenge-helper-scripts/create-next-task.ts +++ b/tools/challenge-helper-scripts/create-next-task.ts @@ -5,18 +5,29 @@ import { getProjectPath } from './helpers/get-project-info'; import { getMetaData, updateMetaData } from './helpers/project-metadata'; import { createChallengeFile, + getChallenge, updateTaskMeta, updateTaskMarkdownFiles } from './utils'; +import { getInputType } from './helpers/get-input-type'; const createNextTask = async () => { const { challengeType } = await newTaskPrompts(); + const meta = getMetaData(); + + const prevChallengeId = + meta.challengeOrder[meta.challengeOrder.length - 1]?.id; + const challengeLang = prevChallengeId && getChallenge(prevChallengeId)?.lang; + + const inputType = await getInputType(challengeType, challengeLang); // Placeholder title, to be replaced by updateTaskMarkdownFiles const options = { title: `Task 0`, dashedName: 'task-0', - challengeType + challengeType, + ...{ ...(challengeLang && { challengeLang }) }, + ...{ ...(inputType && { inputType }) } }; const path = getProjectPath(); @@ -29,7 +40,6 @@ const createNextTask = async () => { createChallengeFile(challengeIdString, challengeText, path); console.log('Finished creating new task markdown file.'); - const meta = getMetaData(); meta.challengeOrder.push({ id: challengeIdString, title: options.title diff --git a/tools/challenge-helper-scripts/helpers/get-challenge-template.ts b/tools/challenge-helper-scripts/helpers/get-challenge-template.ts index 6d012f6b0a9..ca4936e443b 100644 --- a/tools/challenge-helper-scripts/helpers/get-challenge-template.ts +++ b/tools/challenge-helper-scripts/helpers/get-challenge-template.ts @@ -11,19 +11,34 @@ interface ChallengeOptions { dashedName: string; challengeType: string; questionCount?: number; + challengeLang?: string; + inputType?: string; } const buildFrontMatter = ({ challengeId, title, dashedName, - challengeType -}: ChallengeOptions) => `--- + challengeType, + challengeLang, + inputType +}: ChallengeOptions) => { + const langString = challengeLang + ? ` +lang: ${challengeLang}` + : ''; + const inputTypeString = inputType + ? ` +inputType: ${inputType}` + : ''; + + return `--- id: ${challengeId.toString()} title: ${sanitizeTitle(title)} challengeType: ${challengeType} -dashedName: ${dashedName} +dashedName: ${dashedName}${langString}${inputTypeString} ---`; +}; const buildFrontMatterWithVideo = ({ challengeId, diff --git a/tools/challenge-helper-scripts/helpers/get-input-type.ts b/tools/challenge-helper-scripts/helpers/get-input-type.ts new file mode 100644 index 00000000000..310aaebab05 --- /dev/null +++ b/tools/challenge-helper-scripts/helpers/get-input-type.ts @@ -0,0 +1,25 @@ +import { prompt } from 'inquirer'; +import { ChallengeLang } from '../../../shared/config/curriculum'; +import { challengeTypes } from '../../../shared/config/challenge-types'; + +export const getInputType = async ( + challengeType: string, + challengeLang?: string +): Promise => { + const isRequired = + parseInt(challengeType) === challengeTypes.fillInTheBlank && + challengeLang === ChallengeLang.Chinese; + + if (!isRequired) { + return; + } + + const inputType = await prompt<{ value: string }>({ + name: 'value', + message: 'What input type is challenge using?', + type: 'list', + choices: ['pinyin-tone', 'pinyin-to-hanzi'] + }); + + return inputType.value; +}; diff --git a/tools/challenge-helper-scripts/helpers/get-lang-from-superblock.ts b/tools/challenge-helper-scripts/helpers/get-lang-from-superblock.ts new file mode 100644 index 00000000000..a9e17d2d46a --- /dev/null +++ b/tools/challenge-helper-scripts/helpers/get-lang-from-superblock.ts @@ -0,0 +1,15 @@ +import { + ChallengeLang, + SuperBlocks, + superBlockToSpeechLang +} from '../../../shared/config/curriculum'; + +export const getLangFromSuperBlock = ( + superBlock: SuperBlocks +): ChallengeLang => { + const lang = superBlockToSpeechLang[superBlock]; + if (!lang) { + throw new Error(`Missing lang mapping for superBlock: ${superBlock}`); + } + return lang; +}; diff --git a/tools/challenge-helper-scripts/helpers/get-step-template.test.ts b/tools/challenge-helper-scripts/helpers/get-step-template.test.ts index 686faadb0a4..e3e89bc83ae 100644 --- a/tools/challenge-helper-scripts/helpers/get-step-template.test.ts +++ b/tools/challenge-helper-scripts/helpers/get-step-template.test.ts @@ -2,6 +2,24 @@ import { describe, it, expect } from 'vitest'; import ObjectID from 'bson-objectid'; import { getStepTemplate } from './get-step-template'; +const props = { + challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'), + challengeSeeds: [ + { + contents: '', + editableRegionBoundaries: [0, 2], + ext: 'html', + head: '', + id: '', + key: 'indexhtml', + name: 'index', + tail: '' + } + ], + stepNum: 5, + challengeType: 0 +}; + // Note: evaluates at highlevel the process, but seedHeads and seedTails could // be tested if more specifics are needed. describe('getStepTemplate util', () => { @@ -35,24 +53,20 @@ Test 1 --fcc-editable-region-- \`\`\`\n`; - const props = { - challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'), - challengeSeeds: [ - { - contents: '', - editableRegionBoundaries: [0, 2], - ext: 'html', - head: '', - id: '', - key: 'indexhtml', - name: 'index', - tail: '' - } - ], - stepNum: 5, - challengeType: 0 - }; - expect(getStepTemplate(props)).toEqual(baseOutput); }); + + it('should add lang property when challengeLang is passed', () => { + const frontMatter = `--- +id: 60d4ebe4801158d1abe1b18f +title: Step 5 +challengeType: 0 +dashedName: step-5 +lang: es +---`; + + expect(getStepTemplate({ ...props, challengeLang: 'es' })).match( + new RegExp(`^${frontMatter}`) + ); + }); }); diff --git a/tools/challenge-helper-scripts/helpers/get-step-template.ts b/tools/challenge-helper-scripts/helpers/get-step-template.ts index 4c5bf94d65c..08a78db3c77 100644 --- a/tools/challenge-helper-scripts/helpers/get-step-template.ts +++ b/tools/challenge-helper-scripts/helpers/get-step-template.ts @@ -26,6 +26,7 @@ type StepOptions = { stepNum: number; challengeType?: number; isFirstChallenge?: boolean; + challengeLang?: string; }; export interface ChallengeSeed { @@ -42,7 +43,8 @@ function getStepTemplate({ challengeSeeds, stepNum, challengeType, - isFirstChallenge = false + isFirstChallenge = false, + challengeLang }: StepOptions): string { const seedTexts = challengeSeeds .map(({ contents, ext, editableRegionBoundaries }) => { @@ -75,12 +77,17 @@ function getStepTemplate({ demoType: onClick` : ''; + const langString = challengeLang + ? ` +lang: ${challengeLang}` + : ''; + return ( `--- id: ${challengeId.toString()} title: Step ${stepNum} challengeType: ${challengeType ?? 'placeholder'} -dashedName: step-${stepNum}${demoString} +dashedName: step-${stepNum}${langString}${demoString} --- # --description-- diff --git a/tools/challenge-helper-scripts/insert-task.ts b/tools/challenge-helper-scripts/insert-task.ts index 6a40fc8e5d9..c54ad27e279 100644 --- a/tools/challenge-helper-scripts/insert-task.ts +++ b/tools/challenge-helper-scripts/insert-task.ts @@ -5,11 +5,13 @@ import { newTaskPrompts } from './helpers/new-task-prompts'; import { getProjectPath } from './helpers/get-project-info'; import { createChallengeFile, + getChallenge, insertChallengeIntoMeta, updateTaskMeta, updateTaskMarkdownFiles } from './utils'; import { getMetaData } from './helpers/project-metadata'; +import { getInputType } from './helpers/get-input-type'; const insertChallenge = async () => { const challenges = getMetaData().challengeOrder; @@ -22,6 +24,7 @@ const insertChallenge = async () => { value: id })) }); + const challengeLang = getChallenge(challengeAfter.id)?.lang; const indexToInsert = challenges.findIndex( ({ id }) => id === challengeAfter.id @@ -31,10 +34,13 @@ const insertChallenge = async () => { const { challengeType } = await newTaskPrompts(); + const inputType = await getInputType(challengeType, challengeLang); const options = { title: newTaskTitle, dashedName: 'task-0', - challengeType + challengeType, + ...{ ...(challengeLang && { challengeLang }) }, + ...{ ...(inputType && { inputType }) } }; const path = getProjectPath(); diff --git a/tools/challenge-helper-scripts/utils.ts b/tools/challenge-helper-scripts/utils.ts index 4474ec33e6e..eb47dde57f3 100644 --- a/tools/challenge-helper-scripts/utils.ts +++ b/tools/challenge-helper-scripts/utils.ts @@ -22,6 +22,7 @@ interface Options { projectPath?: string; challengeSeeds?: ChallengeSeed[]; isFirstChallenge?: boolean; + challengeLang?: string; } interface QuizOptions { @@ -29,6 +30,7 @@ interface QuizOptions { title: string; dashedName: string; questionCount: number; + challengeLang?: string; } export async function getAllBlocks() { @@ -49,7 +51,8 @@ const createStepFile = ({ challengeType, projectPath = getProjectPath(), challengeSeeds = [], - isFirstChallenge = false + isFirstChallenge = false, + challengeLang }: Options): ObjectID => { const challengeId = new ObjectID(); @@ -58,7 +61,8 @@ const createStepFile = ({ challengeSeeds, stepNum, challengeType, - isFirstChallenge + isFirstChallenge, + challengeLang }); // eslint-disable-next-line @typescript-eslint/no-base-to-string @@ -79,7 +83,8 @@ const createQuizFile = ({ projectPath = getProjectPath(), title, dashedName, - questionCount + questionCount, + challengeLang }: QuizOptions): ObjectID => { const challengeId = new ObjectID(); const challengeType = challengeTypes.quiz.toString(); @@ -90,7 +95,8 @@ const createQuizFile = ({ challengeType, title, dashedName, - questionCount + questionCount, + challengeLang }); // eslint-disable-next-line @typescript-eslint/no-base-to-string fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, quizText); @@ -98,9 +104,11 @@ const createQuizFile = ({ }; const createDialogueFile = ({ - projectPath + projectPath, + challengeLang }: { projectPath: string; + challengeLang: string; }): ObjectID => { const challengeId = new ObjectID(); const challengeType = challengeTypes.dialogue.toString(); @@ -110,7 +118,8 @@ const createDialogueFile = ({ challengeId, challengeType, title: "Dialogue 1: I'm Tom", - dashedName: 'dialogue-1-im-tom' + dashedName: 'dialogue-1-im-tom', + challengeLang }); // eslint-disable-next-line @typescript-eslint/no-base-to-string fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, dialogueText); @@ -267,6 +276,7 @@ const updateTaskMarkdownFiles = (): void => { type Challenge = { challengeType: number; challengeFiles: ChallengeSeed[]; + lang?: string; }; const getChallenge = (challengeId: string): Challenge => {