richdoherty.dev

Japanese Flashcard App

Richard Doherty | November 7, 2022

An image of my flashcard app.

View Project

View Code

Background

I am passionate about languages and linguistics so I figured combining those with programming would be a good idea. That's exactly what I did two years ago with this app. I had a phase years ago where I would learn alphabets for fun. Very nerdy, I know. But hey, I enjoyed it. I learned Hangul and the Cyrillic alphabet, and I was interested in trying Japanese by the time I had the idea for this project. I used flashcards to study the other alphabets I learned so making my own flashcard app to help me learn Japanese and practice programming was a no-brainer.

What I Used to Make This App

This project was primarily done in React, with a small Python script I wrote to convert an excel file into a format I could use with React.

First Steps

The first thing I did was create a new React app by running this command:

npx create-react-app japanese-flashcards
npx create-react-app japanese-flashcards

Next, I made two spreadsheets in Microsoft Excel for both Hiragana and Katakana, where I matched up the characters with their corresponding sounds in English. I wrote a python script, which included a library called pandas, to read the excel file and create a JSON string I could use for the cards.

data.py
# -*- coding: utf-8 -*-
import pandas as pd
 
# Read In Data
df = pd.read_excel("./Hiragana.xlsx", header=0)
 
# Create JSON String
json_string = df.to_json(orient="records", force_ascii=False)
json_string = "var data = " + json_string + ";"
 
# Write to file
# https://stackoverflow.com/questions/16346914/python-3-2-unicodeencodeerror-charmap-codec-cant-encode-character-u2013-i/28041598
text_file = open("Hiragana.js", "w", encoding='utf-8')
text_file.write(json_string)
text_file.close()
data.py
# -*- coding: utf-8 -*-
import pandas as pd
 
# Read In Data
df = pd.read_excel("./Hiragana.xlsx", header=0)
 
# Create JSON String
json_string = df.to_json(orient="records", force_ascii=False)
json_string = "var data = " + json_string + ";"
 
# Write to file
# https://stackoverflow.com/questions/16346914/python-3-2-unicodeencodeerror-charmap-codec-cant-encode-character-u2013-i/28041598
text_file = open("Hiragana.js", "w", encoding='utf-8')
text_file.write(json_string)
text_file.close()

The JSON string would then be formatted like this:

{
    "id": 1,
    "japanese": "",
    "english": "a",
    "category": "Hiragana"
}
{
    "id": 1,
    "japanese": "",
    "english": "a",
    "category": "Hiragana"
}

Looking back on it, simply writing the JSON file and cutting out the extra steps of using Excel and pandas would have been more efficient. At the time, I was interested in trying to write a Python script with pandas that read an Excel file, so that's why I chose to do it that way.

The Flashcards

Now that the Japanese characters are matched up with their English counterparts, we can start on creating the flashcards.

State

First, initialize the values in the state:

App.js
this.state = {
    showAnswer: false,
    initialCharacter: "",
    frontOfCards: "japanese",
    category: "hiragana",
    hiragana: Hiragana,
    katakana: Katakana,
    correct: 0,
    incorrect: 0,
};
App.js
this.state = {
    showAnswer: false,
    initialCharacter: "",
    frontOfCards: "japanese",
    category: "hiragana",
    hiragana: Hiragana,
    katakana: Katakana,
    correct: 0,
    incorrect: 0,
};

Let's go through each of these:

Display the Flashcard

Now it is time to create the flashcard component.

Flashcards.js
import React, { Component } from "react";
 
class Flashcards extends Component {
    render() {
        return (
            <div className="card">
                <a href="#" onClick={() => this.props.flipCard()}>
                    <div className="card-content">
                        <p>{this.props.textToDisplay()}</p>
                    </div>
                </a>
            </div>
        );
    }
}
 
export default Flashcards;
Flashcards.js
import React, { Component } from "react";
 
class Flashcards extends Component {
    render() {
        return (
            <div className="card">
                <a href="#" onClick={() => this.props.flipCard()}>
                    <div className="card-content">
                        <p>{this.props.textToDisplay()}</p>
                    </div>
                </a>
            </div>
        );
    }
}
 
export default Flashcards;

There are two necessary functions for this component, "flipCard" and "textToDisplay."

The "flipCard" function is pretty simple. Whenever the card is clicked, it sets "showAnswer" to true or false, depending on what it is currently set to.

App.js
flipCard() {
    if(this.state.showAnswer === false) {
        this.setState({ showAnswer: true });
    }
    else if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}
App.js
flipCard() {
    if(this.state.showAnswer === false) {
        this.setState({ showAnswer: true });
    }
    else if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}

The "textToDisplay" function checks the state for the values of "frontOfCards" and "showAnswer" to determine whether it will show Japanese or English.

App.js
textToDisplay() {
    if(this.state.frontOfCards === 'japanese') {
        if(this.state.showAnswer === false) {
            return this.state.initialCharacter.japanese;
        }
        else if(this.state.showAnswer === true) {
            return this.state.initialCharacter.english;
        }
    }
    if(this.state.frontOfCards === 'english') {
        if(this.state.showAnswer === false) {
            return this.state.initialCharacter.english;
        }
        else if(this.state.showAnswer === true) {
            return this.state.initialCharacter.japanese;
        }
    }
}
App.js
textToDisplay() {
    if(this.state.frontOfCards === 'japanese') {
        if(this.state.showAnswer === false) {
            return this.state.initialCharacter.japanese;
        }
        else if(this.state.showAnswer === true) {
            return this.state.initialCharacter.english;
        }
    }
    if(this.state.frontOfCards === 'english') {
        if(this.state.showAnswer === false) {
            return this.state.initialCharacter.english;
        }
        else if(this.state.showAnswer === true) {
            return this.state.initialCharacter.japanese;
        }
    }
}

Initial Character

You may remember that "initialCharacter" is still an empty string. That needs to be addressed now. I implemented a function to randomly pick a character out of the list of characters.

App.js
random() {
    if(this.state.category === 'hiragana') {
        const keys = Object.keys(this.state.hiragana);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const letter = this.state.hiragana[randKey];
        return letter;
    }
    else if(this.state.category === 'katakana') {
        const keys = Object.keys(this.state.katakana);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const letter = this.state.katakana[randKey];
        return letter;
    }
}
App.js
random() {
    if(this.state.category === 'hiragana') {
        const keys = Object.keys(this.state.hiragana);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const letter = this.state.hiragana[randKey];
        return letter;
    }
    else if(this.state.category === 'katakana') {
        const keys = Object.keys(this.state.katakana);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const letter = this.state.katakana[randKey];
        return letter;
    }
}

This function is called when the page loads to set "initialCharacter," and whenever the "next" button is clicked.

Toolbar

An image of the toolbar.

Next, another important part of this app is the toolbar. In this component, it will be possible to change the language on the front of the cards, change between Hiragana and Katakana, see the score, and move on to the next card.

The first dropdown controls the language that appears on the front.

App.js
frontDropDown(e) {
    this.setState({ frontOfCards: e.target.value });
    if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}
App.js
frontDropDown(e) {
    this.setState({ frontOfCards: e.target.value });
    if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}

The next sets the alphabet to Hiragana or Katakana.

App.js
categoryDropDown(e) {
    this.setState({ category: e.target.value },
        ()=> {this.setState({ initialCharacter: this.random() })});
    if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}
App.js
categoryDropDown(e) {
    this.setState({ category: e.target.value },
        ()=> {this.setState({ initialCharacter: this.random() })});
    if(this.state.showAnswer === true) {
        this.setState({ showAnswer: false });
    }
}

Finally, the score is determined by the "correct" and "incorrect" variables in the state and is incremented whenever the correct or incorrect buttons are clicked.

Conclusion

These are all the pieces necessary to create a simple flashcard app. If you want to create your own, feel free to fork my repository and swap out the alphabets with one you're interested in. Or you can create your own from scratch using some of the things I wrote about!

Thank you very much for reading this post! I hope you enjoyed it and learned something you can use in your own projects.

GitHubLinkedIn

richdoherty7@gmail.com

This site was built with Next.jsMDXTailwind,  and Vercel.