Papago API를 활용하여 HTTP 통신을 하고 다국어 번역기능을 구현한다.
❗ SwiftUI Framework와 MVVM Design Patterm 기반으로 작성되었습니다.
Picker의 Target Language를 설정하면 그 언어에 맞게 번역된다.
먼저 알아야할 사항들이 있다.
N사의 Papago API는 RESTful API 라고 한다.
위의 RESTful API 문서를 잘 읽어보면,
결국 돌아가는 메커니즘은 Client가 Server와 통신하기 위해 Client가 Request를 하면 Server가 그에 맞는 Response를 해주는 시스템이고, 서로 'Message' 라는 매개체를 가지고 소통한다.
Client가 요청하는 메시지 안에 아래의 3가지가 들어간다.
그중에서, HTTP 헤더는
위와 같이 구성되어진다. (-> 자세한 내용은 MDN의 내용을 참조)
이 구조를 알아야 RESTful API의 원리를 알고 사용할 수 있다.
결국 Papago API를 사용하려면 HTTP 통신에 필요한 구조에 대해 알아야한다.
사용한 API에 대한 정보는 아래 링크에서 확인할 수 있다.
- Papago API Overview
https://developers.naver.com/docs/papago/papago-nmt-overview.md
- Papago API Reference
https://developers.naver.com/docs/papago/papago-nmt-api-reference.md
우선 Papago API가 알려준 내용을 토대로 HTTP 헤더를 형식에 맞추어 완성하면 된다.
let clientID = Bundle.main.CLIENT_ID // 클라이언트 아이디
let clinetSecret = Bundle.main.CLIENT_SECRET // 클라이언트 시크릿
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue(clientID, forHTTPHeaderField: "X-Naver-Client-Id")
request.setValue(clinetSecret, forHTTPHeaderField: "X-Naver-Client-Secret")
URLRequest타입인 request 변수의 .httpMethod 인스턴스 프로퍼티를 "POST"로 설정하여 Message의 Start-Line을 완성한다. (-> '.httpMethod'의 Default 값이 "GET" 이다.)
그리고 .setValue(_:forHTTPHeaderField:) 메서드를 사용하여
Message의 Header Field값을 설정한다.
❗ Client-Id와 Client-Secret의 값은 노출되면 안되는 값이기에 현재는 .plist 파일로 따로 빼놓았다.
Message의 Body Field은
MDN에서 설명한 바와 같이
이 4가지의 경우가 아니면 생략해도 무방하지만, Papago API에서는 POST 방식을 채택하기에 넣어주어야 한다.
let stringWithParameters = "source=ko&target=\(target)&text=\(sourceString)" // Data Query, 원본언어: 한국어 (ko) -> 목적언어: target 파라미터 (즉, 영어(en), 중국어 간체(zh-CN), 일본어(ja))
let data = stringWithParameters.data(using: .utf8)!
request.httpBody = data
.httpBody 인스턴스 프로퍼티는 Data 타입이기에
API에서 알려준 Params 'source', 'target', 'text' 파라미터들을 이용한 Data Query인 stringWithParameters String타입의 상수를
Optional Data 타입을 반환하는 .data(using:) 메서드를 사용하여
Message의 Body Field를 채워주었다.
.data(using:) 메서드에서 using 인수에 .utf8로 설정한 이유는
Request에 성공하면 API에 Response로 주는 결과값이 JSON 데이터 이다.
즉, Request와 Response 과정에서 데이터를 쉽게 Encoding, Decoding 하기 위한 방식으로 UTF-8을 사용했다.
추가로 API의 Content-Type 헤더 필드의 charset이 UTF-8 이다.
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
이제 Request를 해야한다.
let (responseData, _) = try await URLSession.shared.upload(for: request, from: data)
데이터를 요청하고 응답받는 작업은 await 키워드를 이용해 비동기 (= Asynchronous) 처리해주었다.
URLSession을 통해 request하는 메서드는 .upload(for:from:delegate:) 를 사용하면 된다.
이렇게 요청을 했으면 JSON 데이터를 결과값으로 주는 응답값을 받아 decoding 해주어야 하므로,
let response: TranslateResponse = decodeData(responseData)
let translatedString = response.message.result.translatedText
func decodeData <T: Decodable> (_ data: Data) -> T {
do {
return try JSONDecoder().decode(T.self, from: data)
} catch (let error) {
print(error)
preconditionFailure("Fail to decode Data")
}
}
응답값을 decodeData(_ data: Data) 메서드를 사용해 response 변수를 dump() 메서드로 확인해보면
API의 응답 예시처럼 JSON 데이터로 나온다.
여기서 실제로 얻고 싶은 값은 result Key의 Value이기 때문에 JSON 형식에 맞추어
import Foundation
struct Message: Decodable {
let type: String
let service: String
let version: String
let result: Result
enum CodingKeys: String, CodingKey {
case result
case type = "@type"
case service = "@service"
case version = "@version"
}
}
struct Result: Decodable {
let translatedText: String
}
struct TranslateResponse: Decodable {
let message: Message
}
구조체를 생성해주고
let translatedString = response.message.result.translatedText
return translatedString
translatedString 값을 반환해주면 다국어 번역 기능이 완성된다.