├── .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 |
--------------------------------------------------------------------------------