Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions internal/dictionary/dictionary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package dictionary

import (
"encoding/json"
"os"
"strings"

"nihongo-search/internal/models"
)

type Dictionary struct {
KanjiDetails []models.KanjiData
JMDictWords []models.JMDictWord
}

func New() *Dictionary {
return &Dictionary{
KanjiDetails: []models.KanjiData{},
JMDictWords: []models.JMDictWord{},
}
}

func toStringSlice(data interface{}) []string {
var result []string
for _, v := range data.([]interface{}) {
result = append(result, v.(string))
}
return result
}

func toStringMap(data interface{}) map[string]string {
result := make(map[string]string)
for k, v := range data.(map[string]interface{}) {
result[k] = v.(string)
}
return result
}

func (d *Dictionary) LoadKanji(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}

var kanji [][]interface{}
err = json.Unmarshal(data, &kanji)
if err != nil {
return err
}

for _, k := range kanji {
onyomi := k[1].(string)
kunyomi := k[2].(string)
kanjiData := models.KanjiData{
Kanji: k[0].(string),
Onyomi: strings.Fields(onyomi),
Kunyomi: strings.Fields(kunyomi),
Type: k[3].(string),
Meanings: toStringSlice(k[4]),
AdditionalInfo: toStringMap(k[5]),
}
d.KanjiDetails = append(d.KanjiDetails, kanjiData)
}
return nil
}

func (d *Dictionary) LoadJMDict(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}

var iwords [][]interface{}
err = json.Unmarshal(data, &iwords)
if err != nil {
return err
}

for _, iw := range iwords {
d.JMDictWords = append(d.JMDictWords, models.JMDictWord{
Word: iw[0].(string),
Reading: iw[1].(string),
Category: iw[2].(string),
Meanings: toStringSlice(iw[5]),
Identifier: int(iw[6].(float64)),
})
}
return nil
}

func (d *Dictionary) GetKanji(kanji string) []models.KanjiData {
var results []models.KanjiData
for _, kanjiData := range d.KanjiDetails {
if kanjiData.Kanji == kanji {
results = append(results, kanjiData)
}
}
return results
}

func (d *Dictionary) SearchKanjiByMeaning(meaning string) []models.KanjiData {
var results []models.KanjiData
for _, kanjiData := range d.KanjiDetails {
for _, m := range kanjiData.Meanings {
if m == meaning {
results = append(results, kanjiData)
}
}
}
return results
}

func (d *Dictionary) SearchKanjiByReading(reading string, type_ string) []models.KanjiData {
var results []models.KanjiData
if type_ == "onyomi" {
for _, kanjiData := range d.KanjiDetails {
for _, onyomi := range kanjiData.Onyomi {
if strings.Replace(onyomi, ".", "", -1) == reading {
results = append(results, kanjiData)
}
}
}
} else if type_ == "kunyomi" {
for _, kanjiData := range d.KanjiDetails {
for _, kunyomi := range kanjiData.Kunyomi {
if strings.Replace(kunyomi, ".", "", -1) == reading {
results = append(results, kanjiData)
}
}
}
}
return results
}

func (d *Dictionary) SearchJMDictByMeaning(meaning string) []models.JMDictWord {
var results []models.JMDictWord
for _, word := range d.JMDictWords {
for _, m := range word.Meanings {
if strings.ToLower(m) == meaning {
results = append(results, word)
}
}
}
return results
}

func (d *Dictionary) SearchJMDictByReading(reading string) []models.JMDictWord {
var results []models.JMDictWord
// Check if reading has no dots. Wait, why check? The original code did:
// if strings.Replace(reading, ".", "", -1) == reading { ... }
// This implies that if `reading` has a dot, we don't search.
// But `reading` passed here is usually RomajiToKana result, which shouldn't have dots.
// The original code was:
/*
if strings.Replace(reading, ".", "", -1) == reading {
for _, word := range words {
if word.Reading == reading {
results = append(results, word)
}
}
}
*/
// I'll keep the logic same.
if strings.Replace(reading, ".", "", -1) == reading {
for _, word := range d.JMDictWords {
if word.Reading == reading {
results = append(results, word)
}
}
}
return results
}

func (d *Dictionary) GetJMDictWord(word string) []models.JMDictWord {
var results []models.JMDictWord
for _, w := range d.JMDictWords {
if w.Word == word {
results = append(results, w)
}
}
return results
}
18 changes: 18 additions & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package models

type KanjiData struct {
Kanji string
Onyomi []string
Kunyomi []string
Type string
Meanings []string
AdditionalInfo map[string]string
}

type JMDictWord struct {
Word string
Reading string
Category string
Meanings []string
Identifier int
}
67 changes: 67 additions & 0 deletions internal/search/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package search

import (
"sort"
"strconv"
"strings"

"nihongo-search/internal/dictionary"
"nihongo-search/internal/models"
"nihongo-search/lang/ja"
)

type SearchData struct {
KanjiDataList []models.KanjiData
WordDataList []models.JMDictWord
}

type Service struct {
dict *dictionary.Dictionary
}

func NewService(dict *dictionary.Dictionary) *Service {
return &Service{
dict: dict,
}
}

func (s *Service) Search(query string) *SearchData {
query = strings.TrimSpace(query)
data := &SearchData{
KanjiDataList: []models.KanjiData{},
WordDataList: []models.JMDictWord{},
}
if query == "" {
return data
}

// Search Kanji
data.KanjiDataList = append(data.KanjiDataList, s.dict.GetKanji(query)...)
data.KanjiDataList = append(data.KanjiDataList, s.dict.SearchKanjiByMeaning(query)...)

kunyomi := ja.RomajiToKana(query, "hiragana")
data.KanjiDataList = append(data.KanjiDataList, s.dict.SearchKanjiByReading(kunyomi, "kunyomi")...)

onyomi := ja.RomajiToKana(query, "katakana")
data.KanjiDataList = append(data.KanjiDataList, s.dict.SearchKanjiByReading(onyomi, "onyomi")...)

// Sort Kanji
sort.Slice(data.KanjiDataList, func(i, j int) bool {
iFreq, err := strconv.Atoi(data.KanjiDataList[i].AdditionalInfo["freq"])
if err != nil {
iFreq = 99999
}
jFreq, err := strconv.Atoi(data.KanjiDataList[j].AdditionalInfo["freq"])
if err != nil {
jFreq = 99999
}
return iFreq < jFreq
})

// Search Words
data.WordDataList = append(data.WordDataList, s.dict.GetJMDictWord(query)...)
data.WordDataList = append(data.WordDataList, s.dict.SearchJMDictByMeaning(query)...)
data.WordDataList = append(data.WordDataList, s.dict.SearchJMDictByReading(ja.RomajiToKana(query, "hiragana"))...)

return data
}
24 changes: 24 additions & 0 deletions internal/search/search_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package search

import (
"testing"
"nihongo-search/internal/dictionary"
"nihongo-search/internal/models"
)

func TestSearch(t *testing.T) {
dict := dictionary.New()
// Add some dummy data
dict.KanjiDetails = append(dict.KanjiDetails, models.KanjiData{
Kanji: "日",
Meanings: []string{"day", "sun"},
AdditionalInfo: map[string]string{"freq": "1"},
})

srv := NewService(dict)
results := srv.Search("day")

if len(results.KanjiDataList) == 0 {
t.Errorf("Expected results, got 0")
}
}
95 changes: 95 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package server

import (
"fmt"
"html/template"
"net/http"
"nihongo-search/internal/search"
)

type PageData struct {
Title string
}

type Server struct {
searchService *search.Service
templates map[string]*template.Template
}

func NewServer(searchService *search.Service) *Server {
s := &Server{
searchService: searchService,
templates: make(map[string]*template.Template),
}
s.loadTemplates()
return s
}

func (s *Server) loadTemplates() {
s.templates["home"] = template.Must(template.ParseFiles("templates/index.html"))
s.templates["search"] = template.Must(template.ParseFiles("partials/search.html"))
s.templates["pango"] = template.Must(template.ParseFiles("partials/pango/search.html"))
s.templates["text"] = template.Must(template.ParseFiles("partials/text/search.html"))
}

func (s *Server) Start(port string) error {
http.HandleFunc("GET /", s.handleHome)
http.HandleFunc("GET /healthcheck", s.handleHealthCheck)
http.HandleFunc("GET /partial/search", s.handlePartialSearch)
http.HandleFunc("GET /partial/pango/search", s.handlePangoPartialSearch)
http.HandleFunc("GET /partial/text/search", s.handleTextPartialSearch)

fmt.Printf("Server running on port %s\n", port)
return http.ListenAndServe(":"+port, nil)
}

func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
data := PageData{
Title: "Nihongo search!",
}
s.templates["home"].Execute(w, data)
}

func (s *Server) handleHealthCheck(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("OK"))
}

func (s *Server) handlePartialSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
fmt.Println("Query:", query)

data := s.searchService.Search(query)
s.templates["search"].Execute(w, data)
}

func (s *Server) handlePangoPartialSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
fmt.Println("Query:", query)

data := s.searchService.Search(query)

if len(data.KanjiDataList) > 3 {
data.KanjiDataList = data.KanjiDataList[:3]
}
if len(data.WordDataList) > 3 {
data.WordDataList = data.WordDataList[:3]
}

s.templates["pango"].Execute(w, data)
}

func (s *Server) handleTextPartialSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
fmt.Println("Query:", query)

data := s.searchService.Search(query)

if len(data.KanjiDataList) > 3 {
data.KanjiDataList = data.KanjiDataList[:3]
}
if len(data.WordDataList) > 3 {
data.WordDataList = data.WordDataList[:3]
}

s.templates["text"].Execute(w, data)
}
Loading