Construct English dictation App for learners with JavaScript and Vue

We will create a web application for English learners to do dictation.

code

See the Pen English Dictation App by Masato Takamaru (@masato-takamaru) on CodePen.

You can click on an English sentence to hear it as audio.

<html>
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>
const en = "In recent years, governments around the world have been working to raise awareness about oral health. While many people have heard their teeth multiple times per day is a good habit, they most likely have not considered the reasons why this is crucial."
const RootComponent = {
  data () { return {
    sentence: en,
    text: setData(),    //initialization
    p: [],              //correct and incorrect
    showBackward: true, //show/hide the Back to previous button
    message: ''         //response
  }},
  template: `
    <p>Click the sentence, and you can listen to it:</p>
    <div @click="speak(sentence)">
      <p>
        <span v-for="word in text.words">
          {{word+" "}}
        </span>
      </p>
    </div>
    <div>
      <button
        v-for="(option, index) in text.opt"
        v-show="text.optShow[index]"
        @click="input(index)">
        {{option}}
      </button>
    </div>
    <button v-show="showBackward" @click="backward">Back to previous</button>
    <div>{{message}}</div>`,
  methods: {
    input(index) {
      this.text.optShow[index] = false;  //Hide the pressed button.
      //Replace the masked underline with the word you chose from the choices.
      this.text.words[this.text.mask[this.p.length]] = this.text.opt[index];
      this.text.record.push(index);   //Record the number of the button pressed.
      //If the string of the choice matches the that of the text, the answer is correct.
      if(this.text.optMask[index] == this.text.mask[this.p.length]) {
        //Add the string 'correct' to the array if it is correct
        this.p.push('correct');
      } else {
        //If the answer is incorrect, add the string 'incorrect' to the array
        this.p.push('incorrect');
      }
      if(this.p.length == this.text.mask.length) {
        this.showBackward = false;  //Hide the "Back to previous" button.
        //Check if all elements of an array are correct.
        if(this.p.every(value => value == 'correct')) {
          this.message = 'All correct';
        } else {
          this.message = 'There are some errors.';
        }
      }
    },
    speak(elem) {
      speechSynthesis.cancel();     //Cancel any audio that is playing.
      let uttr = new SpeechSynthesisUtterance();  //instance
      const voices = speechSynthesis.getVoices(); //Retrieving the voice list
      let voice;
      voices.forEach((elem) => {
        if(elem.name == 'Google US English') voice = elem;
      });
      uttr.voice  = voice;          //Women's Voices
      uttr.volume = 1.0;            //Volume
      uttr.rate   = 0.8;            //Reading speed
      uttr.pitch  = 1.05;           //pitch
      uttr.lang   = 'en-US';        //language
      uttr.text   = elem;           //text
      speechSynthesis.speak(uttr);  //play
    },
    backward() {
      if(this.p.length > 0) {
        //Delete the last element of the array and go back one.
        this.p.pop();
        //Restore underlined words as they are entered
        this.text.words[this.text.mask[this.p.length]] = ' ____ ';
        //Show the button again.
        this.text.optShow[this.text.record.pop()] = true;
      }
    }
  },
  mounted() {
    const uttr = new SpeechSynthesisUtterance();
    const voices = window.speechSynthesis.getVoices();
  }
}
function setData() {
  let obj = {
    words: en.split(' '),   //Split the text into words.
    record: [],             //Record the order in which buttons are pressed.
    mask: [],               //Position of the word to mask
    opt: [],                //strings of buttons
    optMask: [],            //Position of the word the choice represents
    optShow: []             //Show/hide buttons
  }
  let n = Array.from(Array(obj.words.length).keys()); //Array of sequential numbers
  //shuffle
  for(let i = 0; i < 100; i++) {
    let a = Math.floor(Math.random() * n.length);
    let b = Math.floor(Math.random() * n.length);
    [n[a], n[b]] = [n[b], n[a]];
  }
  //Number of words to mask: 1 word + 25% of the total
  let amount = 1 + Math.round(obj.words.length * 0.15);
  for(let i = 0; i < amount; i++) {
    obj.mask.push(n[i]);            //Add the position of the word to be masked
    obj.opt.push(obj.words[n[i]]);  //Add a string of choices.
    obj.optMask.push(n[i]);         //Add the position of the word the choice represents
    obj.optShow.push('true');       //Add button display
  }
  obj.mask.sort((a, b) => a - b);
  //Replace masked words with underlines
  for(let i = 0; i < obj.opt.length; i++) {
    obj.words[obj.mask[i]] = ' ____ ';
  }
  return obj;
}
const app = Vue.createApp(RootComponent);
app.mount('#root');

Initially, execute the instance of speechsynthesis with mounted(). This is because the first getvoice() will fail. When you run getvoice() for the first time, you will get an empty array.

Words in a sentence have a 15% chance of being masked. The masked words will be displayed as buttons, and pressing them will replace the underlined words.

When you press the “back to previous” button, you will be taken back to the previous state. The buttons you press are recorded and the information is used to return to the previous state.