はじめに
賢い翻訳AIのDeepLを使うにあたり、タイプセーフな通信ライブラリAPIKitの存在を知ったので、PromiseKitと一緒に使ってみました。DeepLのAuth Keyをクライアントに持たせるのは危ういので実際にはこの方法は使わないですが、Alamofireから乗り換えようかなと思ってます。
- DeepL API
50万文字/月までの翻訳は無料で使えるステキなry。 - APIKit
タイプセーフなAPIプログラミングを可能としてくれるステキなry。 - PromiseKit
非同期処理を書くときにクロージャーの入れ子から開放してくれるステキなry。
Decodable準拠
DeepL APIのレスポンスは以下のようなJSON形式なので、JSONDecoderでdecodeできるように定義します。
{
"translations": [{
"detected_source_language":"EN",
"text":"Hallo, Welt!"
}]
}
struct DeepLResponse: Decodable {
let translations: [Translation]
}
struct Translation: Decodable {
let text: String
let detectedSourceLanguage: String
}
DataParser準拠
DataParser準拠のParserを定義します。contentTypeは"application/json"、parseメソッドでJSONDecoderを使用します。DeepLResponseタイプはcamel caseで、DeepLのレスポンスはsnake caseなので、keyDecodingStrategyにconvertFromSnakeCaseを指定します。
final class JSONDecodableDataParser<T:Decodable>:DataParser {
var contentType:String? {
return "application/json"
}
func parse(data:Data) throws -> Any {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
return try jsonDecoder.decode(T.self, from:data)
}
}
Request準拠
DeepL APIの呼び出しに合わせてリクエストプロトコルを定義します。
Content-Typeは、application/x-www-form-urlencodedです。
auth_key=your_auth_key&text=Hello, world&target_lang=DE
GETメソッドも対応していますが、安全のためPOSTメソッドを使用します。
POSTメソッドの場合は、bodyParametersでFormURLEncodedBodyParametersを使えば良いようです。
struct DeepLRequest: Request {
typealias Response = DeepLResponse
let authKey:String
let text:String
let targetLang:String
let sourceLang:String?
var baseURL:URL {
URL(string:"https://api-free.deepl.com")!
}
var method:HTTPMethod {
.post
}
var path:String {
"/v2/translate"
}
var bodyParameters:BodyParameters? {
var params = ["auth_key":authKey,
"text":text,
"target_lang":targetLang]
if let sourceLang = sourceLang {
params["source_lang"] = sourceLang
}
return FormURLEncodedBodyParameters(formObject:params)
}
var dataParser:DataParser {
JSONDecodableDataParser<Response>()
}
func response(from object:Any, urlResponse:HTTPURLResponse) throws -> Response {
guard let object = object as? Response else {
throw ResponseError.unexpectedObject(object)
}
return object
}
}
Mix-in
PromiseKitを使ってDeepLApiCallableプロトコルを定義します。
protocol DeepLApiCallable {
func translate(text:String, targetLang:String, sourceLang:String?) -> Promise<String>
}
extension DeepLApiCallable {
func translate(text:String, targetLang:String, sourceLang:String?=nil) -> Promise<String> {
return Promise<String> { resolver in
let request = DeepLRequest(authKey:your_auth_key, text:text, targetLang:targetLang, sourceLang:sourceLang)
Session.send(request) { result in
switch result {
case .success(let deepLResponse):
resolver.fulfill(deepLResponse.translations[0].text)
case .failure(let error):
resolver.reject(error)
}
}
}
}
}
使用例
struct SomeClass:DeepLApiCallable {...}
let someClass = SomeClass()
firstly {
someClass.translate(text:"こんにちは", targetLang:"EN")
}.done { text in
// 翻訳結果を利用
}.catch { error in
// エラー処理
}