Documentation to assist in the development of the Duo Explained project.
The content scripts are responsible for injecting data into the Duolingo web application and handling the communication between the Duolingo exercises and our ChatGPT module.
The lesson module parses the exercises and provides the necessary context for the exaplantion.
Two sections of the page are parsed, the Question
(or Challenge
, as it is called in the Duolingo codebase) and the Answer
. Two Events are also introduced, that carry the parsed data and are triggered when each of the data is available.
The Question
section is parsed by the Challenge Parsers and the parsed data is carried by the challenge
event.
document.addEventListener('challenge', (event) => {
const challenge = event.detail;
console.log(challenge);
});
This event is triggered whenever a new question is presented to the user.
The event.detail
object has the following structure:
{
type: 'translate',
question: 'Write this in English',
wrapper: '<HTMLElement>',
content: '<parsed data>'
}
The Challenge Parsers
are responsible for parsing the Question
section of the page and extracting the necessary data. The parsed data is then carried by the challenge
event.
Because Duolingo exercises are dynamic and can change, the Challenge Parsers
are divided into different parsers, each responsible for a different type of exercise.
The challenges and their respective data are:
Same approach goes to reverseAssist
and transliterationAssist
.
Select the correct option that translates the sentence provided.
{
choices: [
{option: 1, text: '固く'},
...
],
sentence: 'hard',
userAnswer: {option: 1, text: '固く'}
}
Type the missing word from a translation.
{
sentence: 'an important street',
answer: 'une <blank> importante',
userAnswer: 'une rue importante'
}
Fill in the blanks in a sentence using one of the options provided.
{
options: [
{
option: 1,
choices: ['marcher', 'la voiture']
},
...
],
sentence: "j'aime <blank>, je ne prends pas <blank>.",
userAnswer: "j'aime marcher, je ne prends pas la voiture."
}
Same approach goes to characterMatch
.
Select the matching pairs between a source list and a target list.
{
source: {
choices: [
{option: 1, text: '固く'},
...
],
language: 'ja'
},
target: {
choices: [
{option: 6, text: 'human'},
...
],
language: 'en'
}
}
Read the sentence and complete the answer provided with one of the options.
{
answer: "Aujourd'hui, il...",
choices: [
{option: 1, text: 'prend le métro'},
...
],
sentence: "Je dois travailler aujourd'hui...",
userAnswer: {option: 1, text: 'prend le métro'}
}
Same approach goes to characterSelect
.
Select the correct option from a list of vocabulary.
The choices
may contain an image.
{
choices: [
{option: 1, text: '固く', image: 'https://...'},
...
],
userAnswer: {option: 1, text: '固く', image: 'https://...'}
}
Speak the sentence that is presented.
{
sentence: "Émile! Tu n'as pas...",
language: 'fr'
}
Complete the sentence by tapping the words from the word bank in the correct order.
The sentence
object provides the sentence with the <blank>
placeholders.
The userAnswer
array contains the words that the user selected in that exact order.
{
sentence: "Je dois <blank> à la gare. Nous <blank> à Paris.",
userAnswer: ["aller", "allons"],
wordBank: ["allons", "aller"]
}
Same approach goes to syllableTap
.
Translate a sentence from one language to another.
This may or may not have a word bank
(it will be an empty array if it doesn't). The user answer will also be an array if word bank is present.
{
sentence: 'Paris est une ville importante.',
userAnswer: ['Paris', 'is', ...], // or 'Paris is an important city.'
wordBank: ['Where', 'important', ...] // or []
}
Transliterate the given word.
{
word: '固く',
language: 'ja',
userAnswer: 'kataku'
}