Back

Building a Dictionary App including Audio Pronunciation

Building a Dictionary App including Audio Pronunciation

Knowing how to pronounce words correctly is a must when learning a foreign language. This article will show you how to use React to build a dictionary app that will also speak words out loud as an aid to learning.

In the realm of language learning apps like dictionaries, the quest for fluency often hinges on a crucial element: impeccable audio pronunciation. Just as sculptors rely on a fine chisel, language learners depend on accurate pronunciation to improve their understanding and communication skills. This is where high-quality audio pronunciations come into play. Learners are no longer left deciphering sounds from text alone. They become immersed in the language’s symphony, attuned to the rhythm, intonation, and subtle nuances that bring words to life. This fosters accuracy, boosts confidence, and transforms the struggle to grasp spoken language into a joyful dance of comprehension.

In this project, we will build an interactive dictionary app using React. Unlike traditional dictionary apps, the focus will extend beyond text-based definitions and include audio pronunciation of the words.

Project Setup

We will create a new React project using the create-react-app. This tool offers a streamlined process for initializing React applications with all the essential configurations already in place. Here’s how we can proceed:

  1. Install Node.js and npm

Before creating our React project, ensure that Node.js and npm (Node Package Manager) are installed on your system. You can download and install them from the official Node.js website.

  1. Create a new React project

Open your terminal or command prompt. Navigate to the directory where you want to create your React project. Run the following command to create a new React project using create-react-app.

npx create-react-app interactive-dictionary-app

Replace interactive-dictionary-app with your preferred project name. This command will initialize a new React project with the specified name in a directory.

  1. Navigate to the project After the project is created, navigate to the newly created directory
cd interactive-dictionary-app
  1. Install Tailwind Tailwind will be used for the styling, so ensure you have it installed. To do that, run this command.
npm install -D tailwindcss
npx tailwindcss init

There are a few setups to ensure tailwind works on the project. Visit the Docs to understand better.

  1. Start the development server

After navigating to your project directory, Start the development server.

npm start
  1. Verify the project structure

Once inside the project directory, you’ll find the following folders.

Folder and Files

Project Structure

The project structure consists of several components and modules responsible for the app’s functionality. Here’s an overview of the main components:

  • API module (GetWords.js): This module fetches word definitions and audio pronunciations from an external API. Here is the link to the API

  • AudioPlayer component: Responsible for rendering an audio player interface to play word pronunciations.

  • MeaningDisplay component: Displays the meanings, parts of speech, definitions, examples, and synonyms of a word.

  • SearchBar component: Provides a search bar interface for users to input their search queries.

  • WordDisplay component: Renders the word itself, its phonetic transcription, and pronunciation audio player.

  • App component: The main component responsible for orchestrating the interactions between the other components and managing the application state.

component

Now, let’s explore each component and module in detail, examining their functionality and how they interact.

API module (GetWords.js)

This component is responsible for fetching data from an external API. Let’s examine its implementation details, and its interactions with the rest of the application will be covered later in the article.

export async function fetch_words(word) {
  const dictionaryApi = `https://api.dictionaryapi.dev/api/v2/entries/en/${word}`;
  try {
    // Fetch data from the API
    const response = await fetch(dictionaryApi);
    // Parse the JSON response
    const data = await response.json();
    // Return the fetched data
    return data;
  } catch (error) {
    // Handle errors
    console.error("Error fetching data:", error);
  }
}

We define an asynchronous function named fetch_words. This function takes a single parameter named word, which represents the word entered by the user. Inside the function, we construct the API URL dynamically. We achieve this by leveraging template literals to insert the user-entered word into the dictionaryApi variable, which holds the base URL for the API. This combination creates the complete API endpoint specific to the user’s input. Following the URL construction, we utilize the fetch function to make a GET request to the constructed API endpoint. This request retrieves the data associated with the user-entered word from the external dictionary API. We then parse the JSON response using response.json() and return the fetched data.

If there are any errors during the fetching process, we catch them and log them to the console. This API module serves as the foundation for retrieving words, definitions, and audio pronunciations, which will be used by other components in our application.

App component

The App component serves as the main entry point for our React application. It imports several components used in the application and a function for fetching data from an external API.

import React, { useState } from "react";
import Search from "./Components/SearchBar";
import Meaning from "./Components/MeaningDisplay";
import { fetch_words } from "./API/GetWords";
import Word from "./Components/WordDisplay";

function App() {
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(false);
  const [searchInitiated, setSearchInitiated] = useState(false);
  async function fetch_data(word) {
    try {
      setLoading(true);
      setSearchInitiated(true);
      const get_data = await fetch_words(word);
      setData({ ...get_data[0] });
      setLoading(false);
    } catch (error) {
      console.log(error);
    }
  }
  return (
    <div className="m-16">
      <Search callback={fetch_data} />
      {loading ? (
        <p className="mt-18 font-bold text-2xl">Loading...</p>
      ) : (
        <>
          {searchInitiated && Object.keys(data).length === 0 ? (
            <p className="mt-18 font-bold text-2xl">No Words Found</p>
          ) : (
            <>
              <Word data={data} />
              <Meaning data={data} />
            </>
          )}
        </>
      )}
    </div>
  );
}
export default App;

Let’s break down its structure and functionality.

import React, { useState } from "react";
import Search from "./Components/SearchBar";
import Meaning from "./Components/MeaningDisplay";
import { fetch_words } from "./API/GetWords";
import Word from "./Components/WordDisplay";

We import the necessary React hooks and components from their respective files. This includes the SearchBar, MeaningDisplay, and WordDisplay components, as well as the fetch_words function from the GetWords API module.

function App() {}
export default App;

Next, the App component is defined as a functional component named App and exported using the export default App.

const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
const [searchInitiated, setSearchInitiated] = useState(false);

Here, we defined different states to manage the application state. data: Stores the data fetched from the API. loading: Tracks whether data is currently being fetched. searchInitiated: Tracks whether a search query has been initiated.

async function fetch_data(word) {
  try {
    setLoading(true);
    setSearchInitiated(true);
    const get_data = await fetch_words(word);
    setData({ ...get_data[0] });
    setLoading(false);
  } catch (error) {
    console.log(error);
  }
}

We define an asynchronous function named fetch_data that takes a word parameter. On search, we set the loading and searchInitiated state variables to true to indicate that data fetching has started.

We use the fetch_words function to fetch data from the API based on the provided word parameter. Once the data is fetched successfully, we update the data state variable with the fetched data. If an error occurs during the fetch process, we catch the error and log it to the console.

return (
  <div className="m-16">
    <Search callback={fetch_data} />
    {loading ? (
      <p className="mt-18 font-bold text-2xl">Loading...</p>
    ) : (
      <>
        {searchInitiated && Object.keys(data).length === 0 ? (
          <p className="mt-18 font-bold text-2xl">No Words Found</p>
        ) : (
          <>
            <Word data={data} />
            <Meaning data={data} />
          </>
        )}
      </>
    )}
  </div>
);
  • We return Javascript, which represents the user interface of the component.
  • We render the SearchBar component and pass the fetch_data function as a callback prop.
  • We conditionally render different elements based on the current state:

If loading is true, we display a loading message. If searchInitiated is true and no data has been fetched, we display a message indicating that no words were found. If data has been fetched successfully, we render the WordDisplay and MeaningDisplay components, passing the fetched data as prop which we will still use in the children component.

Search component

The Search component provides a user interface for entering words to search for their definitions and pronunciations.

import React, { useState } from "react";
import { AiOutlineSearch } from "react-icons/ai";
import { LiaTimesSolid } from "react-icons/lia";

function Search({ callback }) {
  const [word, setWord] = useState("");
  const [icon, setIcon] = useState("search");

  function handleKeyDown(e) {
    if (e.key === "Enter" && word) {
      callback(word);
      setIcon("cancel");
    }
  }
  function handleIconClick() {
    if (icon === "cancel") {
      setWord("");
      setIcon("search");
    }
    if (icon === "search" && word) {
      callback(word);
    }
  }
  function handleChange(e) {
    setWord(e.target.value);
  }
  return (
    <div className="relative mb-4 mt-5 ml-1 sm:ml-0">
      <input
        onKeyDown={handleKeyDown}
        onChange={handleChange}
        value={word}
        placeholder="Search a word"
        className="border-solid focus:outline-none border-5 rounded-lg w-full bg-slate-100 text-slate-950 px-2 p-2 placeholder-slate-950 font-bold"
      />
      <button
        className="absolute top-2.5 right-3 translate-y-1 cursor-pointer text-violet-600"
        onClick={handleIconClick}>
        {icon === "search" ? <AiOutlineSearch /> : <LiaTimesSolid />}
      </button>
    </div>
  );
}
export default Search;

Let’s walk through the code above:

import React, { useState } from "react";
import { AiOutlineSearch } from "react-icons/ai";
import { LiaTimesSolid } from "react-icons/lia";

We started by importing useState from React and two icons for the search and cancel buttons from the React icons library. To make the react icons work in your code, you have to install react icons on your terminal using the following command.

npm install react-icons

Next is defining a functional component.

function Search({ callback }) {}
export default Search;

A functional component named Search is defined. The callback prop is a function passed from the parent component, the App Component. We can directly access the callback prop within the component. The callback function is responsible for handling the user input, typically by making an API request to fetch data based on the entered word. The export default Search makes it visible to other modules.

const [word, setWord] = useState("");
const [icon, setIcon] = useState("search");

We use the useState hook to declare two state variables, word and icon, and their corresponding setter functions, setWord and setIcon. useState("") initializes the word state variable with an empty string, and useState("search") initializes the icon state variable with the value “search”.

function handleKeyDown(e) {
  if (e.key === "Enter" && word) {
    callback(word);
    setIcon("cancel");
  }
}

We define a function handleKeyDown that takes an event parameter e representing a key press event. This function is called when a key is pressed within the input field. The condition e.key === "Enter" && word checks if the Enter key is pressed and if the word state variable is not empty. If both conditions are met, we call the callback function passed as a prop with the current value of the word state variable. We also set the icon state variable to “cancel” to change the icon displayed in the search button. Recall that the callback function is from the parent component, the App. The callback, refers to the fetch_data function, which handles the logic.

function handleIconClick() {
  if (icon === "cancel") {
    setWord("");
    setIcon("search");
  }
  if (icon === "search" && word) {
    callback(word);
  }
}

The handleIconClick function here toggles the search icon between “search” and “cancel” when clicked. If the current icon state is “cancel”, it resets the word state to an empty string and changes the icon state back to “search”. If the icon state variable is set to “search” and the word state variable is not empty, we call the callback function with the current value of the word state variable.

function handleChange(e) {
  setWord(e.target.value);
}

We define a function handleChange that updates the word state variable whenever there is a change in the input field. The e.target.value represents the current value of the input field.

return (
  <div className="relative mb-4 mt-5 ml-1 sm:ml-0">
    <input
      onKeyDown={handleKeyDown}
      onChange={handleChange}
      value={word}
      placeholder="Search a word"
      className="border-solid focus:outline-none border-5 rounded-lg w-full bg-slate-100 text-slate-950 px-2 p-2 placeholder-slate-950 font-bold"
    />
    <button
      className="absolute top-2.5 right-3 translate-y-1 cursor-pointer text-violet-600"
      onClick={handleIconClick}>
      {icon === "search" ? <AiOutlineSearch /> : <LiaTimesSolid />}
    </button>
  </div>
);

Finally, we return the javascript code that represents the UI of the Search component. This includes an input field for entering search queries and a button for initiating or canceling the search operation. The onKeyDown, onChange, and onClick event handlers are attached to the respective elements to handle user interactions.

We should have a search input that looks like this, which handles our search queries.

Screenshot (25)

WordDisplay component

The WordDisplay component renders the word along with its phonetic pronunciation, if available. It uses state and effect hooks to dynamically manage and update the phonetic information.

import React, { useEffect, useState } from "react";
import AudioPlayer from "./AudioPlayer";

function Word({ data }) {
  const [phonetic, setPhonetic] = useState({});
  useEffect(() => {
    if (data.meanings) {
      const filteredPhonetics = data.phonetics.filter(
        (phonetic) => phonetic.audio !== ""
      );
      setPhonetic({ ...filteredPhonetics[0] });
    }
  }, [data]);
  return (
    <div className="mb-4 flex justify-between">
      {data.meanings && (
        <>
          <div>
            <div>
              <h2
                className="font-bold text-3xl text-slate-100
            ">
                {" "}
                {data.word}
              </h2>
            </div>
            <div>
              <p className=" text-purple-500 font-medium">
                {phonetic.text ? phonetic.text : data.phonetic}
              </p>
            </div>
          </div>
          {phonetic.audio && (
            <div>
              <AudioPlayer audioFile={phonetic.audio} />
            </div>
          )}
        </>
      )}
    </div>
  );
}
export default Word;

Let’s break down the code and understand how they work and what they do.

import React, { useEffect, useState } from "react";
import AudioPlayer from "./AudioPlayer";

We start by importing the necessary react hooks that will be needed, which are useState and useEffect. The AudioPlayer component will also be used here.

function Word({ data }) {}
export default Word;

This line declares a functional component named Word. The component accepts a single prop named data, which contains information about the word to be displayed. The line export default Word makes it visible to other modules.

const [phonetic, setPhonetic] = useState({});

This line declares a state variable named phonetic using the useState hook. The state will hold the phonetic pronunciation for the word.

useEffect(() => {
  if (data.meanings) {
    const filteredPhonetics = data.phonetics.filter(
      (phonetic) => phonetic.audio !== ""
    );
    setPhonetic({ ...filteredPhonetics[0] });
  }
}, [data]);

This useEffect hook is used to perform side effects in the component. It runs after every render and accepts a function as its first argument. In this case, the effect is triggered whenever the data prop changes. Inside the function, we check if meanings are available in the data, filter out phonetics with audio files, and set the first filtered phonetic as the state.

return (
  <div className="mb-4 flex justify-between">
    {data.meanings && <>(/* Content */)</>}
  </div>
);

This code checks if the meanings property exists in the data object before rendering its contents. If data.meanings is truthy, the content inside the bracket is rendered.

  <div>
    <h2 className="font-bold text-3xl text-slate-100 ">{data.word}</h2>
  </div>
  <div>
    <p className="text-purple-500 font-medium">
      {phonetic.text ? phonetic.text : data.phonetic}
    </p>
  </div>

For the content, the word and its phonetic pronunciation are rendered. If phonetic text is available, it’s displayed; otherwise, we want to use the default phonetic text directly from the data.

{
  phonetic.audio && (
    <div>
      <AudioPlayer audioFile={phonetic.audio} />
    </div>
  );
}

This part checks if an audio file is available for pronunciation. If phonetic.audio is true, an AudioPlayer component is rendered with the audio file passed as a prop.

This is how the word and its phonetic transcription will be rendered.

Screenshot (27)

AudioPlayer component

The AudioPlayer component provides a simple interface for playing audio files. It consists of a button. When clicked, it initiates playback of the specified audio file. The component encapsulates the logic for audio playback and can be easily reused in the application.

import React from "react";
import { BsFillPlayFill } from "react-icons/bs";

function AudioPlayer({ audioFile }) {
  const playMusic = () => {
    const audio = new Audio(audioFile);
    audio.play();
  };
  return (
    <>
      <button
        onClick={playMusic}
        className="bg-purple-200 rounded-full w-14 h-14 flex justify-center items-center mt-2">
        <BsFillPlayFill className="  text-purple-500 w-7 h-7" />
      </button>
    </>
  );
}

export default AudioPlayer;

Let’s look at the logic in this component and how the codes work.

import React from "react";
import { BsFillPlayFill } from "react-icons/bs";

So, First, we want to import React and an icon that will be used as the play button icon.

function AudioPlayer({ audioFile }) {}
export default AudioPlayer;

Then, we have our functional component for playing the phonetic audio. The component accepts a single prop named audioFile, which is the URL of the audio file to be played. Remember, this prop comes from the parent, which is the WordDisplay component.

const playMusic = () => {
  const audio = new Audio(audioFile);
  audio.play();
};

This code defines a function named playMusic that creates a new Audio object with the provided audio file URL and calls the play method to start playback.

return (
  <>
    <button
      onClick={playMusic}
      className="bg-purple-200 rounded-full w-14 h-14 flex justify-center items-center mt-2">
      <BsFillPlayFill className="  text-purple-500 w-7 h-7" />
    </button>
  </>
);

This code renders a button element that triggers the playMusic function when clicked. Inside the button, the BsFillPlayFill icon from the react-icons library represents the play icon.

The audio button should be rendered this way whenever an audio file is available in the dictionary API.

Screenshot (26)

MeaningDisplay component

The MeaningDisplay component renders a word’s meanings along with its part of speech, definitions, examples, synonyms, and source URLs. It dynamically generates these based on the data provided via prop in the parent component in the App.js.

import React from "react";

function Meaning({ data }) {
  console.log("Received data in meaning component:", data);
  return (
    <div>
      {data.meanings && (
        <>
          {data.meanings.map((meaning, index) => (
            <>
              <div key={index}>
                <div className="flex items-center">
                  <h2 className="mb-3 font-bold">{meaning.partOfSpeech} </h2>
                  <p className="border-b border-gray-400 grow ml-2"></p>
                </div>
                <p className=" mb-3 text-slate-400">Meaning</p>
                {meaning.definitions.map((definition) => (
                  <>
                    <h2 className=" mb-2 mx-4">{definition.definition} </h2>
                    {definition.example && (
                      <h2 className="mx-4  text-slate-400">
                        {definition.example}
                      </h2>
                    )}
                  </>
                ))}
              </div>
              {meaning.synonyms.length > 0 && (
                <div className="relative mb-4 ">
                  <p
                    className=" text-slate-400
              ">
                    Synonyms
                  </p>
                  <div>
                    <h2 className="absolute bottom-0 left-20 text-purple-700 font-medium ">
                      {meaning.synonyms[0]}
                    </h2>
                  </div>
                </div>
              )}
            </>
          ))}
          <div className="relative mt-4">
            <p className="border-b border-gray-400 mb-3"></p>
            <p className=" text-slate-400 font-thin">Source</p>
            <div>
              <p className="absolute bottom-0 left-16 ">{data.sourceUrls[0]}</p>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

export default Meaning;

This is an overview of the MeaningDisplay component. Let’s look at how it works.

function Meaning({ data }) {}
export default Meaning;

A functional component named Meaning is declared. The component accepts a single prop named data, which contains information about the whole content of the Dictionary API.

return <div>{data.meanings && <>/* Contents */</>}</div>;

This code checks if the meanings property exists in the data object before rendering its contents.

{
  data.meanings.map((meaning, index) => <div key={index}></div>);
}

Then, we map over the array of meanings in the data prop and render each meaning. Each meaning is wrapped in a <div> element with a unique key based on its index.

<h2 className="mb-3 font-bold">{meaning.partOfSpeech}</h2>

Here, part of speech (e.g., noun, verb) of the word’s meaning is displayed. The part of speech is gotten from the meaning object in the current iteration.

{
  meaning.definitions.map((definition) => (
    <div key={definition.definition}>
      <h2 className="mb-2 mx-4">{definition.definition}</h2>
      {definition.example && (
        <h2 className="mx-4 text-slate-400">{definition.example}</h2>
      )}
    </div>
  ));
}

This code maps over the array of definitions for each meaning and renders them. Each definition is displayed along with an optional example, if available.

{
  meaning.synonyms.length > 0 && (
    <div className="relative mb-4">
      <p className="text-slate-400">Synonyms</p>
      <div>
        <h2 className="absolute bottom-0 left-20 text-purple-700 font-medium">
          {meaning.synonyms[0]}
        </h2>
      </div>
    </div>
  );
}

This part checks if there are any synonyms available for the current meaning. If synonyms exist, the first synonym is displayed.

<p className="absolute bottom-0 left-16">{data.sourceUrls[0]}</p>

This line displays the dictionary entry’s source URL. It assumes that the source URL is available as the first item in the sourceUrls array within the data prop.

Voila! We’ve brought our vision to life, crafting an interactive dictionary equipped with audio pronunciation. This user-friendly tool empowers exploration and learning, empowering users to confidently navigate the fascinating world of languages. The link to the GitHub Repo is here.

Conclusion

The Dictionary app leverages an API integration to provide users with comprehensive word definitions, synonyms, parts of speech, phonetics, and pronunciation. Through state management in React, we ensure smooth communication between components, enabling seamless updates and interactions. User interaction and input handling are prioritized, with a user-friendly search feature facilitating easy word lookup.

Overall, the application offers key features such as real-time word search, dynamic definition display, and audio pronunciation playback. You can explore further customization options, such as implementing dark mode/light mode themes, different styling, font selector features, and more.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay