Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
blmage committed Jul 6, 2019
1 parent d7b56c9 commit 751bd7d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 1 deletion.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
# duolingo-unicode-normalizer
# Duolingo Unicode Normalizer

A minimal browser extension for automatically normalizing inputs on Duolingo.

### The problem(s)

Typos are detected in seemingly perfectly valid answers:

![you_have_a_typo](https://user-images.githubusercontent.com/25432517/60756496-39199c00-9ffe-11e9-817c-cbd3dd337ac3.png)

Searches for existing words (with accented characters) do not return any result:

![no_word_found](https://user-images.githubusercontent.com/25432517/60760068-5a46b080-a02f-11e9-81a3-3901721b481d.png)

### The cause

Duolingo expects answers and dictionary searches to be __NFC-normalized__ strings
(see the [Unicode Normalization FAQ](https://unicode.org/faq/normalization.html)).

But, depending on the input method, those values may contain __decomposed characters__,
making them __internally different__ from their normalized versions.

For example, the Vietnamese word _một_ (_one_) can be written in (at least)
__3 different ways__:

| Version | Value | Escaped value |
| ------------| ----- |---------------- |
| NFC | một | m [\u1ED9](https://unicode-table.com/en/1ED9/) t |
| VN keyboard | một | m [\u00F4](https://unicode-table.com/en/00F4/) [\u0323](https://unicode-table.com/en/0323/) t |
| NFD | một | mo [\u0323](https://unicode-table.com/en/0323/) [\u0302](https://unicode-table.com/en/0302/) t |

### The solution

Normalize relevant inputs before they are handled by Duolingo!

![you_are_correct](https://user-images.githubusercontent.com/25432517/60757095-8d287e80-a006-11e9-981c-5ec363575b8b.png)

![translations_found](https://user-images.githubusercontent.com/25432517/60760069-5b77dd80-a02f-11e9-9fd3-a328aebf6abc.png)

### What if I still encounter some weird typos, or if my search does not work?

Please do not hesitate to
[post an issue](https://github.com/blmage/duolingo-unicode-normalizer/issues/new),
including as much details as possible so that I can have a try at reproducing it!
Binary file added icons/icon_128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/icon_48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "Duolingo Unicode Normalizer",
"version": "1.0",
"description": "Automatically normalizes Duolingo answers and dictionary searches.",
"permissions": [
"activeTab",
"webRequest",
"webRequestBlocking",
"https://*.duolingo.com/"
],
"background": {
"persistent": true,
"scripts": [ "src/background.js" ]
},
"content_scripts": [
{
"matches": [ "https://*.duolingo.com/*" ],
"js": [ "src/content.js" ]
}
],
"icons": {
"48": "icons/icon_48.png",
"128": "icons/icon_128.png"
},
"manifest_version": 2
}
34 changes: 34 additions & 0 deletions src/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const DICTIONARY_HOSTS = [ 'duolingo-lexicon-prod.duolingo.com' ];
const QUERY_PARAMETER = 'query';

chrome.webRequest.onBeforeRequest.addListener(
function (details) {
if (details.method.toLowerCase() === 'options') {
// Ignore pre-flight requests.
return;
}

if (details.type.toLowerCase() === 'xmlhttprequest') {
const url = new URL(details.url.trim());

if (url.searchParams.has(QUERY_PARAMETER)
&& (DICTIONARY_HOSTS.indexOf(url.host) !== -1)
) {
const query = String(url.searchParams.get(QUERY_PARAMETER));

if (query.trim() !== '') {
const normalizedQuery = query.normalize();

if (query !== normalizedQuery) {
url.searchParams.set(QUERY_PARAMETER, normalizedQuery);
return { redirectUrl: url.toString() };
}
}
}
}

return {};
},
{ urls: [ '<all_urls>' ] },
[ 'blocking', 'requestBody' ]
);
43 changes: 43 additions & 0 deletions src/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(function () {
const ANSWER_INPUT_SELECTOR = 'textarea[data-test="challenge-translate-input"]';
let answerInput = null;
let isNormalizing = false;

function normalizeAnswerValue() {
if (isNormalizing || !answerInput || !answerInput.value) {
return;
}

const answer = String(answerInput.value);

if (answer.trim() !== '') {
const normalizedAnswer = answer.normalize();

if (answer !== normalizedAnswer) {
isNormalizing = true;
answerInput.value = normalizedAnswer;
answerInput.dispatchEvent(new Event('change'));
answerInput.dispatchEvent(new Event('blur'));
isNormalizing = false;
}
}
}

window.setInterval(function () {
let newAnswerInput = document.querySelector(ANSWER_INPUT_SELECTOR);

if (newAnswerInput && (newAnswerInput !== answerInput)) {
answerInput = newAnswerInput;

answerInput.addEventListener('blur', function () {
normalizeAnswerValue();
});

answerInput.addEventListener('keydown', function(e) {
if (13 === e.keyCode) {
normalizeAnswerValue();
}
});
}
}, 100);
})();

0 comments on commit 751bd7d

Please sign in to comment.