import Foundation public enum MyMemoryAPI { public static func translate(_ text: String) async throws -> String { try await translate(text) { try await URLSession.shared.data(for: $0) } } static func translate( _ text: String, load: @Sendable (URLRequest) async throws -> (Data, URLResponse) ) async throws -> String { guard !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { throw Failure(message: "Enter English 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: "en|ja")] 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 en→ja match. if let match = result.matches?.first(where: { $0.segment == text && $0.source.split(separator: "-").first == "en" && $0.target.split(separator: "-").first == "ja" && !$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 } } }