├── .gitignore ├── Tests ├── LinuxMain.swift └── SwiftyTranslateTests │ ├── XCTestManifests.swift │ └── SwiftyTranslateTests.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── README.md ├── Package.swift └── Sources └── SwiftyTranslate └── SwiftyTranslate.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftyTranslateTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftyTranslateTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/SwiftyTranslateTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftyTranslateTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftyTranslate 2 | 3 | Swift Implementation for Google Translate 4 | 5 | ```swift 6 | import SwiftyTranslate 7 | 8 | SwiftyTranslate.translate(text: "Hello World", from: "en", to: "de") { result in 9 | switch result { 10 | case .success(let translation): 11 | print("Translated: \(translation.translated)") 12 | case .failure(let error): 13 | print("Error: \(error)") 14 | } 15 | } 16 | ``` -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftyTranslate", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "SwiftyTranslate", 12 | targets: ["SwiftyTranslate"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 21 | .target( 22 | name: "SwiftyTranslate", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SwiftyTranslateTests", 26 | dependencies: ["SwiftyTranslate"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Tests/SwiftyTranslateTests/SwiftyTranslateTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftyTranslate 3 | 4 | final class SwiftyTranslateTests: XCTestCase { 5 | 6 | func testExample() { 7 | let resultExpectation = expectation(description: "Result") 8 | SwiftyTranslate.translate(text: "Hello World", from: "en", to: "de") { result in 9 | switch result { 10 | case .success(let translation): 11 | XCTAssertEqual(translation.translated, "Hallo Welt") 12 | case .failure(let error): 13 | print("Error: \(error)") 14 | } 15 | resultExpectation.fulfill() 16 | } 17 | waitForExpectations(timeout: 5, handler: nil) 18 | } 19 | 20 | func testStringWithLinebreaks() { 21 | let resultExpectation = expectation(description: "Result") 22 | SwiftyTranslate.translate(text: "Hello World\nThis is a sentence with\nlinebreaks", from: "en", to: "de") { result in 23 | switch result { 24 | case .success(let translation): 25 | XCTAssertEqual(translation.translated, "Hallo Welt\nDies ist ein Satz mit\nZeilenumbrüche") 26 | case .failure(let error): 27 | print("Error: \(error)") 28 | } 29 | resultExpectation.fulfill() 30 | } 31 | waitForExpectations(timeout: 5, handler: nil) 32 | } 33 | 34 | static var allTests = [ 35 | ("testExample", testExample), 36 | ("testStringWithLinebreaks", testStringWithLinebreaks), 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /Sources/SwiftyTranslate/SwiftyTranslate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTranslate.swift 3 | // SwiftyTranslate 4 | // 5 | // Created by Christoph Pageler on 15.12.20. 6 | // 7 | 8 | 9 | import Foundation 10 | #if canImport(FoundationNetworking) 11 | import FoundationNetworking 12 | #endif 13 | 14 | public struct SwiftyTranslate { 15 | 16 | public enum Error: Swift.Error { 17 | 18 | case invalidURL 19 | case noData 20 | case tooManyRequests 21 | case invalidData 22 | 23 | } 24 | 25 | public struct Translation { 26 | 27 | public var origin: String 28 | public var translated: String 29 | 30 | } 31 | 32 | public static func translate(text: String, from: String, to: String, 33 | completion: @escaping (Result) -> Void) { 34 | var urlComponents = URLComponents(string: "https://translate.googleapis.com/translate_a/single")! 35 | urlComponents.queryItems = [ 36 | URLQueryItem(name: "client", value: "gtx"), 37 | URLQueryItem(name: "sl", value: from), 38 | URLQueryItem(name: "tl", value: to), 39 | URLQueryItem(name: "dt", value: "t"), 40 | URLQueryItem(name: "q", value: text), 41 | ] 42 | guard let url = urlComponents.url else { 43 | completion(.failure(.invalidURL)) 44 | return 45 | } 46 | 47 | URLSession.shared.dataTask(with: URLRequest(url: url)) 48 | { (data, response, error) in 49 | guard let data = data, let httpResponse = response as? HTTPURLResponse else { 50 | completion(.failure(.noData)) 51 | return 52 | } 53 | guard httpResponse.statusCode != 429 else { 54 | completion(.failure(.tooManyRequests)) 55 | return 56 | } 57 | guard let object = try? JSONSerialization.jsonObject(with: data, options: []) else { 58 | completion(.failure(.invalidData)) 59 | return 60 | } 61 | 62 | // extract array structure 63 | guard let firstArray = object as? [Any], 64 | let secondArray = firstArray.first as? [Any] 65 | else { 66 | completion(.failure(.invalidData)) 67 | return 68 | } 69 | 70 | // extract result 71 | // strings seperated by \n are seperated in the array 72 | var originParts: [String]? 73 | var resultParts: [String]? 74 | for sectionInSecondArray in secondArray { 75 | guard let sectionResultArray = sectionInSecondArray as? [Any] else { continue } 76 | let sectionResult = sectionResultArray[0..<2] 77 | guard let translated = sectionResult.first as? String, 78 | let origin = sectionResult.last as? String 79 | else { 80 | continue 81 | } 82 | 83 | if originParts == nil { originParts = [] } 84 | originParts?.append(origin) 85 | if resultParts == nil { resultParts = [] } 86 | resultParts?.append(translated) 87 | } 88 | 89 | // (re)join seperated strings 90 | if let originParts = originParts, let resultParts = resultParts { 91 | let origin = originParts.joined() 92 | let translated = resultParts.joined() 93 | completion(.success(Translation(origin: origin, translated: translated))) 94 | } else { 95 | completion(.failure(.invalidData)) 96 | } 97 | }.resume() 98 | } 99 | 100 | } 101 | 102 | #if swift(>=5.5) 103 | @available(iOS 13.0.0, *) 104 | @available(macOS 10.15.0, *) 105 | @available(watchOS 6.0, *) 106 | @available(tvOS 13.0.0, *) 107 | extension SwiftyTranslate { 108 | public static func translate(text: String, from: String, to: String) async throws -> Translation { 109 | try await withCheckedThrowingContinuation({ continuation in 110 | SwiftyTranslate.translate(text: text, from: from, to: to) { result in 111 | switch result { 112 | case .success(let value): 113 | continuation.resume(returning: value) 114 | case .failure(let error): 115 | continuation.resume(throwing: error) 116 | } 117 | } 118 | }) 119 | } 120 | } 121 | #endif 122 | --------------------------------------------------------------------------------