71 lines
3.3 KiB
Swift
71 lines
3.3 KiB
Swift
import Foundation
|
|
|
|
public enum MyMemoryAPI {
|
|
public static func translate(
|
|
_ text: String, sourceLanguage: String = "en", targetLanguage: String = "ja"
|
|
) async throws -> String {
|
|
try await translate(text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) {
|
|
try await URLSession.shared.data(for: $0)
|
|
}
|
|
}
|
|
|
|
static func translate(
|
|
_ text: String, sourceLanguage: String = "en", targetLanguage: String = "ja",
|
|
load: @Sendable (URLRequest) async throws -> (Data, URLResponse)
|
|
) async throws -> String {
|
|
guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
throw Failure(message: "Enter text to translate.")
|
|
}
|
|
guard text.utf8.count <= 500 else {
|
|
throw Failure(message: "MyMemory allows at most 500 UTF-8 bytes per translation. Shorten the text or choose OpenAI.")
|
|
}
|
|
var url = URLComponents(string: "https://api.mymemory.translated.net/get")!
|
|
url.queryItems = [URLQueryItem(name: "q", value: text),
|
|
URLQueryItem(name: "langpair", value: "\(sourceLanguage)|\(targetLanguage)")]
|
|
let request = URLRequest(url: url.url!, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
|
|
let (data, response) = try await load(request)
|
|
try Task.checkCancellation()
|
|
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
|
|
throw Failure(message: "MyMemory request failed. Try again later or choose OpenAI.")
|
|
}
|
|
let result = try JSONDecoder().decode(Response.self, from: data)
|
|
guard result.quotaFinished != true, result.responseStatus != 429 else {
|
|
throw Failure(message: "MyMemory's anonymous daily quota is exhausted. Try later or choose OpenAI.")
|
|
}
|
|
guard result.responseStatus == 200 else {
|
|
throw Failure(message: "MyMemory returned no successful translation. Try again later or choose OpenAI.")
|
|
}
|
|
if let translation = result.responseData?.translatedText,
|
|
!translation.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
return translation
|
|
}
|
|
// The service can rank an empty memory first; only use another exact match for the requested pair.
|
|
if let match = result.matches?.first(where: {
|
|
$0.segment == text && $0.source.split(separator: "-").first == Substring(sourceLanguage)
|
|
&& $0.target.split(separator: "-").first == Substring(targetLanguage)
|
|
&& !$0.translation.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}) {
|
|
return match.translation
|
|
}
|
|
throw Failure(message: "MyMemory returned no successful translation. Try again later or choose OpenAI.")
|
|
}
|
|
|
|
private struct Response: Decodable {
|
|
let responseStatus: Int
|
|
let quotaFinished: Bool?
|
|
let responseData: Translation?
|
|
let matches: [Match]?
|
|
struct Match: Decodable {
|
|
let segment: String
|
|
let translation: String
|
|
let source: String
|
|
let target: String
|
|
}
|
|
struct Translation: Decodable { let translatedText: String? }
|
|
}
|
|
private struct Failure: LocalizedError {
|
|
let message: String
|
|
var errorDescription: String? { message }
|
|
}
|
|
}
|