본문 바로가기
개발하기/Google Extension

[EP.05] 국어사전 API를 사용해 단어 검색 페이지 만들기

by lovedeveloping 2025. 1. 16.
반응형

이번이 마지막 시간입니다. 앞 시간에 코드를 보면 제가 직접 용어들을 입력 해뒀습니다. 따라서 모든 용어가 존재하지 않는 문제가 발생합니다. 그 대안으로 국어사전에서 제공하는 API를 사용하여 단어를 검색 할 수 있도록 페이지를 만들었습니다.

 

해당 페이지에서 단어를 입력하면 국어사전에서 결과를 가져와 단어를 나열 해줍니다.

사진
국어사전 페이지

국어사전 페이지 UI 구현하기

먼저 html 구조를 작성 하겠습니다.

<div id="noteArea" class="feature-area">
  <!-- 검색 헤더 영역 -->
  <div class="dictionary-search-header">
    <div class="dictionary-search-container">
      <span class="material-icons">search</span>
      <input type="text" id="dictionarySearchInput" placeholder="단어 검색...">
      <button id="dictionarySearchButton" class="dictionary-search-button">
        <span class="material-icons">arrow_forward</span>
      </button>
    </div>
  </div>
  
  <!-- 검색 결과 영역 -->
  <div class="dictionary-content">
    <div class="dictionary-result">
      <!-- 검색 전 안내 메시지 -->
      <div class="dictionary-placeholder">
        <span class="material-icons">menu_book</span>
        <p>검색어를 입력하시면<br>표준국어대사전의 결과가 표시됩니다.</p>
      </div>
      
      <!-- 검색 결과 표시 영역 -->
      <div class="dictionary-result-content" style="display: none;">
        <div class="word-header">
          <h3 class="word-title">검색된 단어</h3>
          <span class="word-pronunciation">[발음]</span>
        </div>
        
        <div class="word-meanings">
          <div class="word-type">품사</div>
          <ol class="meaning-list">
            <li class="meaning-item">
              <span class="meaning-text">단어의 뜻이 여기에 표시됩니다.</span>
            </li>
          </ol>
        </div>
      </div>
    </div>
  </div>
</div>

그 다음으로는 CSS를 사용하여 꾸며보겠습니다.

/* 검색창 컨테이너 스타일 */
.dictionary-search-container {
  display: flex;
  align-items: center;
  padding: 8px 16px;
  background: white;
  border: 1px solid #dfe1e5;
  border-radius: 24px;
  margin: 16px;
  transition: all 0.2s ease;
}

.dictionary-search-container:focus-within {
  border-color: #1a73e8;
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}

/* 검색 아이콘 스타일 */
.dictionary-search-container .material-icons {
  color: #5f6368;
  margin-right: 8px;
  font-size: 20px;
}

/* 검색 입력창 스타일 */
#dictionarySearchInput {
  border: none;
  outline: none;
  width: 100%;
  font-size: 14px;
  color: #3c4043;
  background: transparent;
  margin-right: 8px;
}

#dictionarySearchInput::placeholder {
  color: #80868b;
}

/* 검색 결과 영역 스타일 */
.dictionary-content {
  padding: 16px;
  height: calc(100% - 85px);
  overflow-y: auto;
}

/* 검색 전 안내 메시지 스타일 */
.dictionary-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 200px;
  color: #5f6368;
  text-align: center;
}

.dictionary-placeholder .material-icons {
  font-size: 48px;
  margin-bottom: 16px;
  color: #1a73e8;
}

/* 검색 결과 카드 스타일 */
.dictionary-result-content {
  background: white;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
  padding: 20px;
}

/* 단어 제목 영역 스타일 */
.word-header {
  margin-bottom: 16px;
}

.word-title {
  font-size: 20px;
  font-weight: 500;
  color: #1a73e8;
  margin: 0 0 4px 0;
}

.word-pronunciation {
  font-size: 14px;
  color: #5f6368;
}

/* 단어 의미 영역 스타일 */
.word-type {
  font-size: 13px;
  color: #202124;
  font-weight: 500;
  margin-bottom: 8px;
}

.meaning-list {
  margin: 0;
  padding-left: 20px;
}

.meaning-item {
  font-size: 14px;
  color: #3c4043;
  line-height: 1.6;
  margin-bottom: 8px;
}

구조는 간단합니다. 바로 다음 기능으로 가시죠!

API를 사용하여 국어사전 결과 불러오기

js/ config.js 파일 생성 

해당 파일에는 국어사전에서 제공받은 API 키를 입력 할 것입니다. 하루 제한 50,000건 이고 추가 금액은 없으니 안심입니다.

// 하루 50,000번 제한 있음 -> 더 사용 한다고 추가 금 안 나온다. 따라서 이거 봐도 사용은 자유롭게 해도 상관 없음 
// 사용자도 없을 것 같긴 하지만 뭐 어때 

export const CONFIG = {
  DICTIONARY_API_KEY: '제공 받은 키 입력',
  DICTIONARY_API_URL: 'https://stdict.korean.go.kr/api/search.do'
};

js/dictionaryManager.js 파일 생성

해당 파일에는 국어사전 API를 가져와 원하는 단어를 검색창에 입력하여 결과를 가져와 띄우는 작업을 합니다.

import { CONFIG } from './config.js';

class DictionaryManager {
  constructor() {
    // DOM 요소 초기화
    this.searchInput = document.getElementById('dictionarySearchInput');
    this.searchButton = document.getElementById('dictionarySearchButton');
    this.resultContainer = document.querySelector('.dictionary-result');
    this.placeholder = document.querySelector('.dictionary-placeholder');
    this.resultContent = document.querySelector('.dictionary-result-content');
    
    this.initializeEventListeners();
  }

  // 이벤트 리스너 설정
  initializeEventListeners() {
    // 검색 버튼 클릭 시 검색 실행
    this.searchButton.addEventListener('click', () => this.handleSearch());
    
    // Enter 키 입력 시 검색 실행
    this.searchInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        this.handleSearch();
      }
    });
  }

  // 검색 처리
  async handleSearch() {
    const searchTerm = this.searchInput.value.trim();
    if (!searchTerm) return;

    try {
      this.showLoading();
      const result = await this.searchDictionary(searchTerm);
      this.displayResult(result);
    } catch (error) {
      this.showError(error.message);
    }
  }

  // API 호출
  async searchDictionary(query) {
    const params = new URLSearchParams({
      key: CONFIG.DICTIONARY_API_KEY,
      q: query,
      req_type: 'json',
      type1: 'word',
      pos: 1
    });

    try {
      const response = await fetch(`${CONFIG.DICTIONARY_API_URL}?${params}`);
      if (!response.ok) {
        throw new Error('API 요청에 실패했습니다.');
      }
      
      const data = await response.json();
      return this.parseApiResponse(data);
    } catch (error) {
      throw new Error('검색 중 오류가 발생했습니다.');
    }
  }

  // API 응답 데이터 파싱
  parseApiResponse(data) {
    if (!data.channel || !data.channel.item) {
      throw new Error('검색 결과가 없습니다.');
    }

    return data.channel.item.map(item => ({
      word: item.word,
      pronunciation: item.pronunciation || '',
      pos: item.pos || '',
      definition: item.sense.definition.replace(/^\d+\.\s*/, '')
    }));
  }

  // 검색 결과 화면 표시
  displayResult(results) {
    this.placeholder.style.display = 'none';
    this.resultContent.style.display = 'block';

    const firstResult = results[0];
    this.resultContent.innerHTML = `
      <div class="word-header">
        <h3 class="word-title">${firstResult.word}</h3>
        ${firstResult.pronunciation ? 
          `<span class="word-pronunciation">[${firstResult.pronunciation}]</span>` : ''}
      </div>
      
      <div class="word-meanings">
        ${firstResult.pos ? 
          `<div class="word-type">${firstResult.pos}</div>` : ''}
        <ol class="meaning-list">
          ${results.map(result => `
            <li class="meaning-item">
              <span class="meaning-text">${result.definition}</span>
            </li>
          `).join('')}
        </ol>
      </div>
    `;
  }

  // 로딩 상태 표시
  showLoading() {
    this.resultContent.style.display = 'none';
    this.placeholder.innerHTML = `
      <span class="material-icons loading">sync</span>
      <p>검색 중입니다...</p>
    `;
    this.placeholder.style.display = 'flex';
  }

  // 에러 메시지 표시
  showError(message) {
    this.resultContent.style.display = 'none';
    this.placeholder.innerHTML = `
      <span class="material-icons">error_outline</span>
      <p>${message}</p>
    `;
    this.placeholder.style.display = 'flex';
  }
}

// 인스턴스 생성
const dictionaryManager = new DictionaryManager();

제공하는 예제 자료를 활용 한 것입니다. 복잡해 보이긴 합니다만 아직 코딩 능력이 부족해 양해 부탁드립니다.
결과는 가장 처음 사진과 같이 잘뜹니다! 이제 저희는 드디어 끝났습니다~ 

하루 1개씩 올릴려고 했는 데, 흠.. 죄송합니다 그게 쉽지는 않네요.  현재 크롬 확장 스토어에 저의 프로그램이 올라가있습니다.

사진 3
확장 스토어 사진

 

많은 관심 부탁드리고, 언제나 피드백은 환영입니다. 감사합니다.

반응형