Skip to content

Instantly share code, notes, and snippets.

@valeIT
Created July 11, 2017 17:13
Show Gist options
  • Save valeIT/6a0b8517b94921f653c6cb334a1a4015 to your computer and use it in GitHub Desktop.
Save valeIT/6a0b8517b94921f653c6cb334a1a4015 to your computer and use it in GitHub Desktop.
//
// TranslatorManager.swift
//
// Copyright © 2017 Valentino Urbano. (http://www.valentinourbano.com)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import Alamofire
class TranslatorManager {
private init() {}
static let sharedInstance = TranslatorManager()
enum TranslatorState: Int {
case Initial = 0
case InProgress = 1
case Completed = 2
}
enum TranslatorError: Int
{
case UnableToTranslate = 0
case NetworkError = 1
case Same = 2
case TranslationInProgress = 3
case AlreadyTranslated = 4
case MissingCredentials = 5
case NoToken = 6
case BadRequest = 7
case Other = 8
}
private let TRANSLATOR_ERROR_DOMAIN = "TranslatorErrorDomain"
var preferSourceGuess: Bool = false
var translatorState:TranslatorState = .Initial
var completionHandler: TranslatorCompletionHandler? = nil
var operation: DataRequest? = nil
private var apiKey = ""
typealias TranslatorCompletionHandler = (_ error: NSError?, _ translated: String?, _ sourceLanguage: String?) -> ()
func initWithKey(key: String) {
self.apiKey = key
preferSourceGuess = false
translatorState = .Initial
completionHandler = nil
operation = nil
}
func resetWithKey(key: String) {
initWithKey(key: key)
}
func flushCache() {}
//MARK: Supported Languages
func supportedLanguages(_ completion: @escaping (NSError?, [String]?) -> ()) {
if (self.translatorState == .InProgress)
{
let error = errorWithCode(code: TranslatorError.TranslationInProgress.rawValue, description:"translation already in progress")
completion(error, nil);
return;
}
else if (self.translatorState == .Completed)
{
let error = errorWithCode(code: TranslatorError.AlreadyTranslated.rawValue, description:"translation already completed")
completion(error, nil);
return;
}
else
{
self.translatorState = .InProgress;
}
self.operation = TranslateRequest.supportedLanguagesWithKey(key: apiKey, completion: { (languageCodes, error) in
if (error != nil)
{
let errorState: TranslatorError = TranslatorError(rawValue: error!.code) ?? .NetworkError
let fgError = self.errorWithCode(code: errorState.rawValue, description:nil)
completion(fgError, nil);
}else{
completion(nil, languageCodes);
}
self.translatorState = .Completed
})
}
//MARK: Translate
func translateText(_ text: String?, withSource: String? = nil, target: String? = nil, completion: @escaping TranslatorCompletionHandler) {
var source = withSource
var target = target
var text = text
if (self.translatorState == .InProgress)
{
let error = errorWithCode(code: TranslatorError.TranslationInProgress.rawValue, description:"translation already in progress")
completion(error, nil, nil);
return;
}
else if (self.translatorState == .Completed)
{
let error = errorWithCode(code: TranslatorError.AlreadyTranslated.rawValue, description:"translation already completed")
completion(error, nil, nil);
return;
}
else
{
self.translatorState = .InProgress;
}
// check cache for existing translation
// NSDictionary *cached = [[PINCache sharedCache] objectForKey:[self cacheKeyForText:text target:target]];
// if (cached)
// {
// NSString *cachedSource = [cached objectForKey:@"src"];
// NSString *cachedTranslation = [cached objectForKey:@"txt"];
//
// NSLog(@"FGTranslator: returning cached translation");
//
// completion(nil, cachedTranslation, cachedSource);
// return;
// }
source = self.filteredLanguageCodeFromCode(code: source)
if (target == nil) {
target = self.filteredLanguageCodeFromCode(code: NSLocale.preferredLanguages[0])
} else {
target = self.filteredLanguageCodeFromCode(code: target)
}
if (source?.lowercased() == target) {
source = nil
}
if (self.preferSourceGuess && self.shouldGuessSourceWithText(text: text)){
source = nil;
}
self.completionHandler = completion;
self.operation = TranslateRequest.translateMessage(key: apiKey, text: text, withSource: source, target: target, completion: { (translatedMessage, detectedSource, error) in
if (error != nil) {
self.handleError(error: error!)
}else{
self.handleSuccessWithOriginal(original: text, translatedMessage:translatedMessage, detectedSource:detectedSource, target:target)
self.translatorState = .Completed
}
})
}
//MARK: Shared
private func handleError(error: NSError)
{
let errorState: TranslatorError = TranslatorError(rawValue: error.code) ?? TranslatorError.NetworkError
let fgError = self.errorWithCode(code: errorState.rawValue, description:nil)
if ((self.completionHandler) != nil) {
self.completionHandler?(fgError, nil, nil)
}
}
private func handleSuccessWithOriginal(original: String?, translatedMessage: String?,detectedSource: String?, target: String?)
{
self.completionHandler?(nil, translatedMessage, detectedSource)
// [self cacheText:original translated:translatedMessage source:detectedSource target:target];
}
private func cancel()
{
self.completionHandler = nil;
self.operation?.cancel()
}
//MARK: Utility
private func errorWithCode(code: Int, description: String? = nil) -> NSError
{
var userInfo: [AnyHashable: Any] = [:]
if (description != nil) {
userInfo[NSLocalizedDescriptionKey] = description
}
return NSError(domain:TRANSLATOR_ERROR_DOMAIN, code:code, userInfo:userInfo)
}
private func shouldGuessSourceWithText(text: String?) -> Bool
{
if text == nil {
return false
}
let nsstring = text! as NSString
let five: UInt = 5
let tfive: UInt = 25
let f = nsstring.wordCount() >= five
let s = nsstring.wordCharacterCount() >= tfive
return f && s
}
// massage languge code to make Google Translate happy
func filteredLanguageCodeFromCode(code: String?) -> String?
{
if code == nil { return nil }
let nsstring = code! as NSString
if (nsstring.length <= 3) {
return nsstring as String;
}
if (nsstring.isEqual(to: "zh-Hant") || nsstring.isEqual(to: "zh-TW")) {
return "zh-TW";
}else if (nsstring.hasSuffix("input")) {
// use phone's default language if crazy (keyboard) inputs are detected
return NSLocale.preferredLanguages[0]
}else{
// trim stuff like en-GB to just en which Google Translate understands
return nsstring.substring(to: 2)
}
}
}
struct TranslateRequest {
private static let baseURL = "https://translate.yandex.net/api/v1.5/tr.json/"
static func translateMessage(key: String, text: String?, withSource source: String?, target: String?, completion: @escaping (_ translatedMessage: String?, _ detectedSource: String?, _ error: NSError?) -> ()) -> DataRequest {
let url = "translate"
let finalUrl = URL(string: TranslateRequest.baseURL)!.appendingPathComponent(url)
var targetLanguage = source
if targetLanguage != nil && target != nil {
targetLanguage?.append("-\(target!)")
} else {
targetLanguage = target
}
let encoding = URLEncoding(destination: URLEncoding.Destination.httpBody)
var json: [String: Any] = [:]
json["key"] = key
if let text = text {
json["text"] = text
}
if let targetLanguage = targetLanguage {
json["lang"] = targetLanguage
}
return Alamofire.request(finalUrl, method: HTTPMethod.post, parameters: json, encoding: encoding, headers: nil).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let value):
if let value = value as? [String: Any] {
if let code = value["code"] as? Int, let message = value["message"] as? String {
completion(nil, nil, NSError(domain: "Network_", code: code, userInfo: [NSLocalizedDescriptionKey: message]))
return
} else if let value = value as? [String:Any], let text = value["text"] as? [String], text.count > 0 {
completion(text.first!, source, nil)
return
} else {
completion(nil, nil, NSError(domain: "Network_", code: 0, userInfo: nil))
return
}
} else {
completion(nil, nil, NSError(domain: "Network_", code: 0, userInfo: nil))
return
}
case .failure(let error):
completion(nil, nil, error as NSError?)
return
}
})
}
static func supportedLanguagesWithKey(key: String, completion: @escaping (_ languageCodes: [String]?, _ error: NSError?) -> ()) -> DataRequest {
let url = "getLangs"
let finalUrl = URL(string: TranslateRequest.baseURL)!.appendingPathComponent(url)
let encoding = URLEncoding(destination: URLEncoding.Destination.httpBody)
return Alamofire.request(finalUrl, method: HTTPMethod.post, parameters: ["key":key], encoding: encoding, headers: nil).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let value):
if let value = value as? [String: Any], let inner = value["dirs"] as? [String] {
let drop = inner.map({ (element) -> String in
if element.contains("-") && (element as NSString).length > 3 {
return (element as NSString).substring(to: 2)
}
return element
})
let removeDupe = Array(Set(drop))
completion(removeDupe, nil)
return
} else {
completion(nil, NSError(domain: "Network_", code: 0, userInfo: nil))
return
}
case .failure(let error):
completion(nil, error as NSError?)
return
}
})
}
}
@valeIT
Copy link
Author

valeIT commented Jul 11, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment