Luke's Blog

Writing a NativeScript Angular Dictionary App Part 1

Why?

Normally, I don’t read very much. Sure, I’ll read Facebook posts and Reddit comments, but I normally find reading anything of length tiresome and boring. Recently, I’ve been trying to fix that since I saw something which basically said that most CEOs read at least five or six books  a month. Clearly, the people in top company positions need to be knowledgeable about a wide variety of things in order to stay at the top and make wise decisions. So, I thought to myself, I have to become a reader like them too!

That’s all well and good except I found that I would often times encounter a word that I didn’t know the meaning of and that sometimes threw of my reading throw terribly. There are a number of dictionary apps out there, but you normally have to go to the homescreen to open them, breaking your reading flow in order to get the definition of the word you want.

Possible Solution?

So, I decided to make my own dictionary app. The idea is that it will have a floating bubble like Facebook’s messenger bubbles:

(Not actually Facebook messenger’s chat bubble. It’s the floating workout rest timer bubble for a workout app I use.)

This would prevent the user from having to navigate to the homescreen, open their app drawer and open the dictionary. Instead, they could tap on the chat bubble to open the dictionary quickly.

Except it would prove to be a bit tougher than that. For this app, I wanted to use NativeScript because I really like Angular and I want to move away from using Native Android for the moment. Unfortunately, there is no bubble plugin/feature in NativeScript yet. So, I’ll have to make one for myself eventually once I get better at NativeScript, although it shouldn’t be too hard because NativeScript makes it easy to call native Java code.

So, for now I decided to just go ahead and make the dictionary app. I’ll deal with the floating bubble stuff later because there are other problems to solve with regards to dictionary apps. The most important one is that most of them don’t really handle slang words very well. Sure, the Urban Dictionary app would do well with that, but official dictionaries tend to struggle with that kind of stuff. As an example, the word “wata” cannot be found on dictionary.com, but Urban Dictionary has many definitions for it. However, using Urban Dictionary is not a panacea because often times the Oxford definition for official words is better than the equivalent definition on Urban Dictionary. So, the simple solution is to make a dictionary app that uses both of their APIs to serve results.

Birth of Cortex Dictionary

Cortex currently solves the latter problem of combining the two dictionary APIs by basically creating a generic Word model and transforming the responses from both APIs, which have very different API result designs, to fit.

Get it on Google Play

 

 

I’m just going to show you the basics behind how Cortex combines the two API results into one. I won’t be getting into details such as storing the state and the actual layout code. To get started, I needed to know the “shape” of the results of each API. Here’s what I now get when using the NativeScript Chrome debugger when searching for the word “and”:

Urban Dictionary API

GET http://api.urbandictionary.com/v0/define?term=and  
{
  "tags": [
    "the",
    "is",
    "a",
    "i",
    "you",
    "sex",
    "of",
    "love",
    "it",
    "to"
  ],
  "result_type": "exact",
  "list": [
    {
      "definition": "A conjunction to link two clauses together.",
      "permalink": "http:\/\/and.urbanup.com\/1086001",
      "thumbs_up": 289,
      "author": "Kieran Jones",
      "word": "and",
      "defid": 1086001,
      "current_vote": "",
      "example": "The dog jumped over the fence AND the cat did too.",
      "thumbs_down": 98
    },
    {
      "definition": "This is an acronym for Asshole Next Door",
      "permalink": "http:\/\/and.urbanup.com\/2111191",
      "thumbs_up": 30,
      "author": "Yoser",
      "word": "AND",
      "defid": 2111191,
      "current_vote": "",
      "example": "Example-The person who lives at 812 Bruner is the AND that keeps me up all night with his loud television.  The same AND thinks his riced-out Mustang looks cool.",
      "thumbs_down": 26
    },
    {
      "definition": "The word that is banned in all English essays from ages 5-10",
      "permalink": "http:\/\/and.urbanup.com\/9287256",
      "thumbs_up": 0,
      "author": "DigBickHarry",
      "word": "and",
      "defid": 9287256,
      "current_vote": "",
      "example": "Teacher: Write about your day\r\nChild: Today we went to the park AND we had fun AND we laughed\r\nTeacher: No ANDS!\r\nChild: Today we went to the park 'n' we had fun 'n' we laughed\r\nTeacher: FUCK THIS SHIT IM OUT",
      "thumbs_down": 2
    }
    ...
  ],
  "sounds": [
    "http:\/\/media.urbandictionary.com\/sound\/and-1258.mp3",
    "http:\/\/media.urbandictionary.com\/sound\/and-14399.mp3",
    "http:\/\/api.twilio.com\/2010-04-01\/Accounts\/ACd09691b82112e4b26fce156d7c01d0ed\/Recordings\/RE32808d1e6811968bcdb8b91f9bea23c3.mp3"
  ]
}

Note how colourful some of these definitions are 🙂

Oxford API

Get Word Matches

GET https://od-api.oxforddictionaries.com/api/v1/search/en?q=and 
{
    "results": [
        {
            "region": "gb",
            "matchString": "and",
            "word": "and",
            "inflection_id": "and",
            "matchType": "inflection",
            "id": "and"
        },
        {
            "word": "-and",
            "region": "gb",
            "matchType": "headword",
            "matchString": "and",
            "id": "-and"
        }
        ...
    ],
    "metadata": {
        "provider": "Oxford University Press",
        "offset": 0,
        "limit": 5000,
        "sourceLanguage": "en",
        "total": 867
    }
}

Get Definition of Aforementioned Word Matches

GET https://od-api.oxforddictionaries.com/api/v1/entries/en/and 
{
    "metadata": {
        "provider": "Oxford University Press"
    },
    "results": [
        {
            "id": "and",
            "language": "en",
            "lexicalEntries": [
                {
                    "entries": [
                        {
                            "etymologies": [
                                "Old English and, ond, of Germanic origin; related to Dutch en and German und"
                            ],
                            "homographNumber": "000",
                            "notes": [
                                {
                                    "text": "It is still widely taught and believed that conjunctions such as and (and also but and because) should not be used to start a sentence, the argument being that a sentence starting with and expresses an incomplete thought and is therefore incorrect. Writers down the centuries have readily ignored this advice, however, using and to start a sentence, typically for rhetorical effect, as in the following example: What are the government's chances of winning in court? And what are the consequences? A small number of verbs, notably try, come, and go can be followed by and with another verb, as in sentences like we're going to try and explain it to them or why don't you come and see the film? The structures in these verbs correspond to the use of the infinitive to, as in we're going to try to explain it to them or why don't you come to see the film? Since these structures are grammatically odd—for example, the use is normally only idiomatic with the infinitive of the verb and not with other forms (i.e. it is not possible to say I tried and explained it to them)—they are regarded as wrong by some traditionalists. However, these uses are extremely common and can certainly be regarded as part of standard English. For information about whether it is more correct to say both the boys and the girls or both the boys and girls, see both",
                                    "type": "editorialNote"
                                }
                            ],
                            "senses": [
                                {
                                    "definitions": [
                                        "used to connect words of the same part of speech, clauses, or sentences, that are to be taken jointly"
                                    ],
                                    "examples": [
                                        {
                                            "text": "a hundred and fifty"
                                        },
                                        {
                                            "text": "they can read and write"
                                        },
                                        {
                                            "text": "bread and butter"
                                        }
                                    ],
                                    "id": "m_en_gbus0031920.013",
                                    "subsenses": [
                                        {
                                            "definitions": [
                                                "used to connect two clauses when the second refers to something that happens after the first"
                                            ],
                                            "examples": [
                                                {
                                                    "text": "he turned round and walked out"
                                                }
                                            ],
                                            "id": "m_en_gbus0031920.017"
                                        },
                                        {
                                            "definitions": [
                                                "used to connect two clauses, the second of which refers to something that results from the first"
                                            ],
                                            "examples": [
                                                {
                                                    "text": "there was a flash flood and by the next morning the town was under water"
                                                }
                                            ],
                                            "id": "m_en_gbus0031920.018"
                                        }
                                    ]
                                },
                                {
                                    "definitions": [
                                        "used to introduce an additional comment or interjection"
                                    ],
                                    "examples": [
                                        {
                                            "text": "if it came to a choice—and this was the worst thing—she would turn her back on her parents"
                                        }
                                    ],
                                    "id": "m_en_gbus0031920.025",
                                    "subsenses": [
                                        {
                                            "definitions": [
                                                "used to introduce a question in connection with what someone else has just said"
                                            ],
                                            "examples": [
                                                {
                                                    "text": "‘I found the letter in her bag.’ ‘And did you steam it open?’"
                                                }
                                            ],
                                            "id": "m_en_gbus0031920.026"
                                        },
                                        {
                                            "definitions": [
                                                "used to introduce a statement about a new topic"
                                            ],
                                            "examples": [
                                                {
                                                    "text": "and now to the dessert"
                                                }
                                            ],
                                            "id": "m_en_gbus0031920.027"
                                        }
                                    ]
                                },
                                {
                                    "definitions": [
                                        "used after some verbs and before another verb to indicate intention, instead of ‘to’"
                                    ],
                                    "examples": [
                                        {
                                            "text": "I would try and do what he said"
                                        }
                                    ],
                                    "id": "m_en_gbus0031920.029",
                                    "registers": [
                                        "informal"
                                    ]
                                }
                                ...
                            ]
                        }
                    ],
                    "language": "en",
                    "lexicalCategory": "Conjunction",
                    "pronunciations": [
                        {
                            "audioFile": "http://audio.oxforddictionaries.com/en/mp3/and_1_gb_1.mp3",
                            "dialects": [
                                "British English"
                            ],
                            "phoneticNotation": "IPA",
                            "phoneticSpelling": "and"
                        },
                        {
                            "audioFile": "http://audio.oxforddictionaries.com/en/mp3/and_gb_3.mp3",
                            "dialects": [
                                "British English"
                            ],
                            "phoneticNotation": "IPA",
                            "phoneticSpelling": "(ə)n"
                        },
                        {
                            "audioFile": "http://audio.oxforddictionaries.com/en/mp3/and_gb_1.mp3",
                            "dialects": [
                                "British English"
                            ],
                            "phoneticNotation": "IPA",
                            "phoneticSpelling": "ənd"
                        }
                    ],
                    "text": "and"
                }
                ...
            ],
            "type": "headword",
            "word": "and"
        }
    ]
}

As you can see, the two APIs are completely different from each other. However, they do provide mostly the same information that we’ll need. Here is part of my Word model :

import { SearchService } from '../services';
import { OxfordResponse } from './oxford-response.model';
import { Definition } from './definition.model';
import { UrbanDictionaryResponse } from './urban-dictionary-response.model';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../environment/environment';
export class Word{
    word:String;
    definition:String|Definition[];
    source:String;
    ...
}

So, let’s look at what we’ve got in this model:

  1. The actual word, “and” in the above API example
  2. A single definition / an array of definitions. Words can have multiple definitions for use in different contexts.
  3. The source. This tracks whether or not the word came from Urban Dictionary or Oxford. This helps with showing the appropriate dictionary’s logo in the result page.

The Word model contains an array of Definition objects, so let’s look at that model:

export class Definition{
    category:String;
    definition:String;

    constructor(obj:any){
        if(obj.category){
            this.category = obj.category;
        }

        if(obj.definition){
            this.definition = obj.definition;
        }
    }
}
  1. The “category” of the definition tracks whether or not this particular definition uses the word as a noun, adjective, etc
  2. The definition is of course the definition text

Response Models

There are a couple response models that I created for the responses for the respective APIs:

OxfordResponse

export class OxfordResponse{
    word: String;
    id: String;
    definition:String;
    constructor(obj:any){
        if(obj.word){
            this.word = obj.word;
        }

        if(obj.id){
            this.id = obj.id;
        }
    }
}

 

UrbanDictionaryResponse

export class UrbanDictionaryResponse{
    word:String;
    definition:String;

    constructor(obj:any){
        if(obj.word){
            this.word = obj.word;
        }
        if(obj.definition){
            this.definition = obj.definition;
        }
    }
}

 

Retrieving and Transforming the Urban Dictionary Response

Here is the code I used to transform the data returned by Urban Dictionary into a usable Word object:

    searchForWordOnUrbanDictionary(word:String){
        return new Promise((resolve, reject) => {
            this.http.get(`${environment.urban_dict_api.base_url}?term=${word.toLowerCase()}`)
                .subscribe(
                    (data:any) => {
                        console.log(data);
                        if(data.result_type === "exact"){
                            let word = new Word(new UrbanDictionaryResponse(data.list[0]));
                            let definitions:Definition[] = [];

                            data.list = data.list.sort((a, b) => {
                                let a_score = a.thumbs_up - a.thumbs_down;
                                let b_score = b.thumbs_up - b.thumbs_down;
                                return a_score - b_score
                            });

                            for(let counter = 0; counter < data.list.length; counter++){ let item:any = data.list[counter]; definitions.push(new Definition({definition:item.definition})); } word.definition = definitions; console.log("Got urban dictionary"); resolve([word]); } else{ console.log("Not exact") } resolve([]); }, (err) => {
                        console.log("error");
                        reject(err);
                    }
                )
        })        
    }

Basically, once the app retrieves the response, it:

  1. Sorts the response’s list attribute by the score (thumbs_up – thumbs_down) of each definition.
  2. Adds the definitions to the definition list for that word.

Retrieving and Transforming the Oxford Dictionary Response

Here is how I get the list of possible word matches:

 

    searchForWordOnOxford(word:String){
        return new Promise((resolve, reject) => {
            let headers:HttpHeaders = new HttpHeaders();
            headers = headers.set("app_id", environment.oxford_api.app_id);
            headers = headers.set("app_key", environment.oxford_api.app_key);
            this.http.get(`${environment.oxford_api.base_url}/search/en?q=${word.toLowerCase()}`, 
                {
                    headers: headers
                })
                .subscribe(
                    (data:any) => {
                        console.log(data.results);
                        let words:Word[] = data.results.slice(0,3).map((item) => {
                            return new OxfordResponse(item);
                        })
                        .map((item:OxfordResponse) => {
                            return new Word(item, this);
                        })
                        resolve(words);
                    },
                    (err) => {
                        reject(err);
                    }
                )
        })
    }

All this does is retrieve the list of possible word/phrase matches. For example, if you searched for “and” you may get “-and” as well as “and” in the response list. In the constructor of the Word model, there is a code block that calls the function that retrieves the definition of the word match. That function is called getOxfordDefinition and it lives in a service file with the other retriever methods mentioned above:

 

    getOxfordDefinition(word:String){
        return new Promise((resolve, reject) => {
            let headers:HttpHeaders = new HttpHeaders();
            headers = headers.set("app_id", environment.oxford_api.app_id);
            headers = headers.set("app_key", environment.oxford_api.app_key);
            this.http.get(`${environment.oxford_api.base_url}/entries/en/${word.toLowerCase()}`, 
                {
                    headers: headers
                })
                .subscribe(
                    (data:any) => {
                        console.log(data);
                        let definitions:Definition[] = [];
                        if(data.results && data.results.length > 0){
                            let result_item = data.results[0];
                            for(let lexical_entry_counter = 0; lexical_entry_counter < result_item.lexicalEntries.length; lexical_entry_counter++){
                                let lexical_entry = result_item.lexicalEntries[lexical_entry_counter];
                                for( let entry_counter = 0; entry_counter < lexical_entry.entries.length; entry_counter++){
                                    let entry = lexical_entry.entries[entry_counter];
                                    for(let senses_counter = 0; senses_counter < entry.senses.length; senses_counter++){ let sense = entry.senses[senses_counter]; sense.definitions.map((item) => {
                                                definitions.push(new Definition(
                                                    {
                                                    category: lexical_entry.lexicalCategory, 
                                                    definition: item
                                                    })
                                                );
                                                return item;
                                            })
                                    }
                                }
                            }
                        }
                        resolve(definitions);
                    },
                    (err) => {
                        reject(err);
                    }
                )
        })
    }

This is a lot more complex than the other methods because the response from the Oxford API is much harder to deal with than the others. The gist of the code is that:

  1.  The definition category can be found in response.results[current].lexicalEntries[currentLexicalEntry].lexicalCategory.
  2. The actual definition text can be found in response.results[current].lexicalEntries[currentLexicalEntry].entries[currentEntry].senses[currentSense],definitions[currentDefinition].

Knowing that, it iterates over the response building an array of Definitions and then returns the result in a promise.

That’s basically how we end up with something like this:

Cortex definition examples

Categories: Personal Projects

1 Comment

  1. This makes sense, often times there are various definitions for any given term and sometimes I don’t fully understand a particular definition so sometimes I have to end up going on multiple tabs looking up the same term. I remember having to do this for I.T exam for CXC, and I particularly remember looking up the term array and could only really get it after reading like 4 definitions. I appreciate this app and solution to this problem.

Leave a Reply

Your email address will not be published.

*

Copyright © 2018 Luke's Blog

Theme by Anders NorenUp ↑