Skip to content

Commit

Permalink
Merge branch 'release/4.5.0' into versions
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Feb 21, 2021
2 parents d0ea356 + 2fb2828 commit 34ca469
Show file tree
Hide file tree
Showing 18 changed files with 197 additions and 17 deletions.
2 changes: 1 addition & 1 deletion BartyCrouch.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "BartyCrouch"
s.version = "4.4.1"
s.version = "4.5.0"
s.summary = "Localization/I18n: Incrementally update/translate your Strings files from .swift, .h, .m(m), .storyboard or .xib files."

s.description = <<-DESC
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ If needed, pluralize to `Tasks`, `PRs` or `Authors` and list multiple entries se
### Security
- None.

## [4.5.0] - 2021-02-21
### Added
- Add support for DeepL API as an alternative for Microsoft Translator API.
PR: [#220](https://github.com/Flinesoft/BartyCrouch/pull/220) | Author: [noppe](https://github.com/noppefoxwolf)

## [4.4.1] - 2021-01-16
### Fixed
- Fixed an issue with unmatching country code casing for Portuguese and Canadian French.
Expand Down
2 changes: 1 addition & 1 deletion Formula/bartycrouch.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Bartycrouch < Formula
desc "Incrementally update/translate your Strings files"
homepage "https://github.com/Flinesoft/BartyCrouch"
url "https://github.com/Flinesoft/BartyCrouch.git", :tag => "4.4.0", :revision => "ac593d4fe3cb895944c1e67fa76046fb925733b6"
url "https://github.com/Flinesoft/BartyCrouch.git", :tag => "4.4.1", :revision => "d0ea3568044e2348e5fa87b9fdbc9254561039e2"
head "https://github.com/Flinesoft/BartyCrouch.git"

depends_on :xcode => ["12.0", :build]
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<img src="https://api.codacy.com/project/badge/Coverage/7b34ad9193c2438aa32aa29a0490451f"/>
</a>
<a href="https://github.com/Flinesoft/BartyCrouch/releases">
<img src="https://img.shields.io/badge/Version-4.4.1-blue.svg"
alt="Version: 4.4.1">
<img src="https://img.shields.io/badge/Version-4.5.0-blue.svg"
alt="Version: 4.5.0">
</a>
<img src="https://img.shields.io/badge/Swift-5.3-FFAC45.svg"
alt="Swift: 5.3">
Expand Down Expand Up @@ -269,7 +269,8 @@ tasks = ["interfaces", "code", "transform", "normalize"]
<details><summary>Options for <code>translate</code></summary>

- `paths`: The directory / directories to search for Strings files.
- `secret`: Your [Microsoft Translator Text API Subscription Key](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup#authentication-key).
- `translator`: Specifies the translation API. Use `microsoftTranslator` or `deepL`.
- `secret`: Your [Microsoft Translator Text API Subscription Key](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup#authentication-key) or [Authentication Key for DeepL API](https://www.deepl.com/pro-account/plan).
- `sourceLocale`: The source language to translate from.

</details>
Expand Down
2 changes: 1 addition & 1 deletion Sources/BartyCrouch/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SwiftCLI
// MARK: - CLI
let cli = CLI(
name: "bartycrouch",
version: "4.4.1",
version: "4.5.0",
description: "Incrementally update & translate your Strings files from code or interface files."
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import Foundation
import MungoHealer
import Toml

enum Translator: String {
case microsoftTranslator
case deepL
}

struct TranslateOptions {
let paths: [String]
let secret: String
let secret: Secret
let sourceLocale: String
}

Expand All @@ -13,9 +18,19 @@ extension TranslateOptions: TomlCodable {
let update: String = "update"
let translate: String = "translate"

if let secret: String = toml.string(update, translate, "secret") {
if let secretString: String = toml.string(update, translate, "secret") {
let translator = toml.string(update, translate, "translator") ?? ""
let paths = toml.filePaths(update, translate, singularKey: "path", pluralKey: "paths")
let sourceLocale: String = toml.string(update, translate, "sourceLocale") ?? "en"
let secret: Secret
switch Translator(rawValue: translator) {
case .microsoftTranslator, .none:
secret = .microsoftTranslator(secret: secretString)

case .deepL:
secret = .deepL(secret: secretString)
}

return TranslateOptions(paths: paths, secret: secret, sourceLocale: sourceLocale)
} else {
throw MungoError(source: .invalidUserInput, message: "Incomplete [update.translate] options provided, ignoring them all.")
Expand All @@ -26,7 +41,14 @@ extension TranslateOptions: TomlCodable {
var lines: [String] = ["[update.translate]"]

lines.append("paths = \(paths)")
lines.append("secret = \"\(secret)\"")
switch secret {
case let .deepL(secret):
lines.append("secret = \"\(secret)\"")

case let .microsoftTranslator(secret):
lines.append("secret = \"\(secret)\"")
}

lines.append("sourceLocale = \"\(sourceLocale)\"")

return lines.joined(separator: "\n")
Expand Down
11 changes: 9 additions & 2 deletions Sources/BartyCrouchKit/FileHandling/StringsFileUpdater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public class StringsFileUpdater {
/// - clientSecret: The Microsoft Translator API Client Secret.
/// - override: Specified if values should be overridden.
/// - Returns: The number of values translated successfully.
public func translateEmptyValues(usingValuesFromStringsFile sourceStringsFilePath: String, clientSecret: String, override: Bool = false) throws -> Int {
public func translateEmptyValues(usingValuesFromStringsFile sourceStringsFilePath: String, clientSecret: Secret, override: Bool = false) throws -> Int {
guard let (sourceLanguage, sourceRegion) = extractLocale(fromPath: sourceStringsFilePath) else {
throw MungoFatalError(
source: .invalidUserInput,
Expand Down Expand Up @@ -256,7 +256,14 @@ public class StringsFileUpdater {
let existingTargetTranslations = findTranslations(inString: oldContentString)
var updatedTargetTranslations: [TranslationEntry] = []

let translator = BartyCrouchTranslator(translationService: .microsoft(subscriptionKey: clientSecret))
let translator: BartyCrouchTranslator
switch clientSecret {
case let .microsoftTranslator(secret):
translator = .init(translationService: .microsoft(subscriptionKey: secret))

case let .deepL(secret):
translator = .init(translationService: .deepL(apiKey: secret))
}

for sourceTranslation in sourceTranslations {
let (sourceKey, sourceValue, sourceComment, sourceLine) = sourceTranslation
Expand Down
6 changes: 6 additions & 0 deletions Sources/BartyCrouchKit/Globals/Secret.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public enum Secret: Equatable {
case microsoftTranslator(secret: String)
case deepL(secret: String)
}
4 changes: 2 additions & 2 deletions Sources/BartyCrouchKit/OldCommandLine/CommandLineActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public class CommandLineActor {
}
}

func actOnTranslate(paths: [String], override: Bool, verbose: Bool, secret: String, locale: String) {
func actOnTranslate(paths: [String], override: Bool, verbose: Bool, secret: Secret, locale: String) {
let inputFilePaths = paths.flatMap { StringsFilesSearch.shared.findAllStringsFiles(within: $0, withLocale: locale) }.withoutDuplicates()

guard !inputFilePaths.isEmpty else { print("No input files found.", level: .warning); return }
Expand Down Expand Up @@ -316,7 +316,7 @@ public class CommandLineActor {
print("Successfully updated strings file(s) of Storyboard or XIB file.", level: .success, file: inputFilePath)
}

private func translate(secret: String, _ inputFilePath: String, _ outputStringsFilePaths: [String], override: Bool, verbose: Bool) {
private func translate(secret: Secret, _ inputFilePath: String, _ outputStringsFilePaths: [String], override: Bool, verbose: Bool) {
var overallTranslatedValuesCount = 0
var filesWithTranslatedValuesCount = 0

Expand Down
18 changes: 18 additions & 0 deletions Sources/BartyCrouchTranslator/BartyCrouchTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ public final class BartyCrouchTranslator {
/// - Parameters:
/// - subscriptionKey: The `Ocp-Apim-Subscription-Key`, also called "Azure secret key" in the docs.
case microsoft(subscriptionKey: String)
case deepL(apiKey: String)
}

private let microsoftProvider = ApiProvider<MicrosoftTranslatorApi>()
private let deepLProvider = ApiProvider<DeepLApi>()

private let translationService: TranslationService

Expand Down Expand Up @@ -50,6 +52,22 @@ public final class BartyCrouchTranslator {
case let .failure(failure):
return .failure(MungoError(source: .internalInconsistency, message: failure.localizedDescription))
}

case let .deepL(apiKey):
var allTranslations: [Translation] = []
for targetLanguage in targetLanguages {
let endpoint = DeepLApi.translate(texts: [text], from: sourceLanguage, to: targetLanguage, apiKey: apiKey)
switch deepLProvider.performRequestAndWait(on: endpoint, decodeBodyTo: DeepLTranslateResponse.self) {
case let .success(translateResponse):
let translations: [Translation] = translateResponse.translations.map({ (targetLanguage, $0.text) })
allTranslations.append(contentsOf: translations)

case let .failure(failure):
return .failure(MungoError(source: .internalInconsistency, message: failure.localizedDescription))
}
}

return .success(allTranslations)
}
}
}
78 changes: 78 additions & 0 deletions Sources/BartyCrouchTranslator/DeeplApi/DeepLApi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Foundation
import Microya

// Documentation can be found here: https://www.deepl.com/ja/docs-api/

enum DeepLApi {
case translate(texts: [String], from: Language, to: Language, apiKey: String)

static let maximumTextsPerRequest: Int = 25
static let maximumTextsLengthPerRequest: Int = 5_000

static func textBatches(forTexts texts: [String]) -> [[String]] {
var batches: [[String]] = []
var currentBatch: [String] = []
var currentBatchTotalLength: Int = 0

for text in texts {
if currentBatch.count < maximumTextsPerRequest && text.count + currentBatchTotalLength < maximumTextsLengthPerRequest {
currentBatch.append(text)
currentBatchTotalLength += text.count
} else {
batches.append(currentBatch)

currentBatch = [text]
currentBatchTotalLength = text.count
}
}

return batches
}
}

extension DeepLApi: Endpoint {
typealias ClientErrorType = DeepLTranslateErrorResponse

var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}

var encoder: JSONEncoder {
JSONEncoder()
}

var baseUrl: URL {
URL(string: "https://api.deepl.com")!
}

var subpath: String {
switch self {
case .translate:
return "/v2/translate"
}
}

var method: HttpMethod {
.get
}

var queryParameters: [String: QueryParameterValue] {
var urlParameters: [String: QueryParameterValue] = [:]

switch self {
case let .translate(texts, sourceLanguage, targetLanguage, apiKey):
urlParameters["text"] = .array(texts)
urlParameters["source_lang"] = .string(sourceLanguage.rawValue.capitalized)
urlParameters["target_lang"] = .string(targetLanguage.rawValue.capitalized)
urlParameters["auth_key"] = .string(apiKey)
}

return urlParameters
}

var headers: [String: String] {
["Content-Type": "application/json"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

struct DeepLTranslateErrorResponse: Decodable {
let message: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

struct DeepLTranslateResponse: Decodable {
struct Translation: Decodable {
let detectedSourceLanguage: String
let text: String
}

let translations: [Translation]
}
2 changes: 1 addition & 1 deletion Sources/SupportingFiles/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.4.1</string>
<string>4.5.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class ConfigurationTests: XCTestCase {
XCTAssertEqual(configuration.updateOptions.normalize.sortByKeys, false)

XCTAssertEqual(configuration.updateOptions.translate!.paths, ["Sources"])
XCTAssertEqual(configuration.updateOptions.translate!.secret, "bingSecret")
XCTAssertEqual(configuration.updateOptions.translate!.secret, Secret.microsoftTranslator(secret: "bingSecret"))
XCTAssertEqual(configuration.updateOptions.translate!.sourceLocale, "de")

XCTAssertEqual(configuration.lintOptions.paths, ["Sources"])
Expand Down
2 changes: 1 addition & 1 deletion Tests/BartyCrouchKitTests/DemoTests/DemoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class DemoTests: XCTestCase {
let microsoftSubscriptionKey = "" // TODO: load from environment variable
guard !microsoftSubscriptionKey.isEmpty else { return }

let translateOptions = TranslateOptions(paths: ["."], secret: microsoftSubscriptionKey, sourceLocale: "en")
let translateOptions = TranslateOptions(paths: ["."], secret: .microsoftTranslator(secret: microsoftSubscriptionKey), sourceLocale: "en")
TranslateTaskHandler(options: translateOptions).perform()

let expectedMessages: [String] = [
Expand Down
28 changes: 28 additions & 0 deletions Tests/BartyCrouchTranslatorTests/DeepLTranslatorApiTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@testable import BartyCrouchTranslator
import Foundation
import Microya
import XCTest

class DeepLTranslatorApiTests: XCTestCase {
func testTranslate() {
let apiKey = "" // TODO: load from environment variable
guard !apiKey.isEmpty else { return }

let endpoint = DeepLApi.translate(
texts: ["How old are you?", "Love"],
from: .english,
to: .german,
apiKey: apiKey
)

let apiProvider = ApiProvider<DeepLApi>()

switch apiProvider.performRequestAndWait(on: endpoint, decodeBodyTo: DeepLTranslateResponse.self) {
case let .success(translateResponses):
XCTAssertEqual(translateResponses.translations[0].text, "Wie alt sind Sie?")

case let .failure(failure):
XCTFail(failure.localizedDescription)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MicrosoftTranslatorApiTests: XCTestCase {
.turkish,
Language.with(languageCode: "pt", region: "BR")!,
Language.with(languageCode: "pt", region: "PT")!,
Language.with(languageCode: "fr", region: "CA")!,
Language.with(languageCode: "fr", region: "CA")!
],
microsoftSubscriptionKey: microsoftSubscriptionKey
)
Expand Down

0 comments on commit 34ca469

Please sign in to comment.