Skip to content

Commit

Permalink
Merge branch 'release/1.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Mar 3, 2016
2 parents ffed1e7 + c79acc8 commit 4bbfd9c
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# BartyCrouch

BartyCrouch can **search a Storyboard/XIB file for localizable strings** and **update your existing localization `.strings` incrementally** by adding new keys, keeping your existing translations and deleting only the ones that are no longer used. BartyCrouch even **keeps changes to your translation comments** given they are enclosed like `/* comment to keep */` and don't span multiple lines.
BartyCrouch can **search a Storyboard/XIB file for localizable strings** and **update your existing localization `.strings` incrementally** by adding new keys, keeping your existing translations and deleting only the ones that are no longer used. BartyCrouch even **updates your translation comments** with updated Base values if their structure was not changed from the Xcode defaults and will **keep changes to your translation comments** if started by a `/*!` (recommended) or the default structure was changed in another way.

Additionally BartyCrouch can now also **automatically translate existing `.strings` files to languages you don't speak** using the Microsoft Translator API. You can exactly **choose the languages to auto-translate** and BartyCrouch will **keep all existing translations** by default.

Expand Down
90 changes: 71 additions & 19 deletions Sources/Code/StringsFileUpdater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class StringsFileUpdater {

/// Updates the keys of this instances strings file with those of the given strings file.
/// Note that this will add new keys, remove not-existing keys but won't touch any existing ones.
public func incrementallyUpdateKeys(withStringsFileAtPath otherStringFilePath: String, addNewValuesAsEmpty: Bool, ignoreKeysWithBaseValueContainingAnyOfStrings: [String] = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"], force: Bool = false) {
public func incrementallyUpdateKeys(withStringsFileAtPath otherStringFilePath: String, addNewValuesAsEmpty: Bool, ignoreKeysWithBaseValueContainingAnyOfStrings: [String] = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"], force: Bool = false, updateCommentWithBase: Bool = true) {

do {
let newContentString = try String(contentsOfFile: otherStringFilePath)
Expand All @@ -39,46 +39,89 @@ public class StringsFileUpdater {
let oldTranslations = self.findTranslationsInLines(self.linesInFile)
let newTranslations = self.findTranslationsInLines(linesInNewFile)

let updatedTranslations: [(key: String, value: String, comment: String?)] = {
let updatedTranslations: [(key: String, value: String, comment: String?)] = try {

var translations: [(key: String, value: String, comment: String?)] = []

for (key, value, comment) in newTranslations {
for (key, newValue, newComment) in newTranslations {

// skip keys marked for ignore
guard !value.containsAny(ofStrings: ignoreKeysWithBaseValueContainingAnyOfStrings) else {
guard !newValue.containsAny(ofStrings: ignoreKeysWithBaseValueContainingAnyOfStrings) else {
continue
}

let updatedValue: String = {

let oldTranslation = oldTranslations.filter{ $0.0 == key }.first
if let existingValue = oldTranslation?.1 {
if !force {
return existingValue
let oldTranslation = oldTranslations.filter{ $0.0 == key }.first

// get value from default comment structure if possible
let oldBaseValue: String? = {
if let oldComment = oldTranslation?.2 {
if let foundMatch = self.defaultCommentStructureMatches(inString: oldComment) {
return (oldComment as NSString).substringWithRange(foundMatch.rangeAtIndex(1))
}
}

if !addNewValuesAsEmpty {
return value
return nil
}()


let updatedComment: String? = {

guard let oldComment = oldTranslation?.2 else {
// add new comment if none existed before
return newComment
}

guard let newComment = newComment else {
// keep old comment if no new comment exists
return oldComment
}

return ""
if force {
// override with comment in force update mode
return newComment
}

if updateCommentWithBase && self.defaultCommentStructureMatches(inString: oldComment) != nil {
// update
return newComment
} else {
return oldComment
}
}()


let updatedComment: String? = {
let updatedValue: String = {

let oldComment = oldTranslations.filter{ $0.0 == key }.first
if let existingComment = oldComment?.2 {
if !force {
return existingComment
let oldTranslation = oldTranslations.filter{ $0.0 == key }.first

guard let oldValue = oldTranslation?.1 else {
if addNewValuesAsEmpty {
// add new key with empty value
return ""
} else {
// add new key with Base value
return newValue
}
}

if force {
// override with new value in force update mode
return newValue
}

if let oldBaseValue = oldBaseValue {
if oldBaseValue == oldValue {
// update base value
return newValue
}
}

return comment
// keep existing translation
return oldValue

}()


let updatedTranslation = (key, updatedValue, updatedComment)
translations.append(updatedTranslation)

Expand All @@ -96,6 +139,15 @@ public class StringsFileUpdater {

}

private func defaultCommentStructureMatches(inString string: String) -> NSTextCheckingResult? {
do {
let defaultCommentStructureRegex = try NSRegularExpression(pattern: "\\A Class = \".*\"; .* = \"(.*)\"; ObjectID = \".*\"; \\z", options: .CaseInsensitive)
return defaultCommentStructureRegex.matchesInString(string, options: .ReportCompletion, range: NSMakeRange(0, string.characters.count)).first
} catch {
return nil
}
}

/// Rewrites file with specified translations and reloads lines from new file.
func rewriteFileWithTranslations(translations: [(key: String, value: String, comment: String?)]) {

Expand Down
Binary file modified Tests/Assets/StringsFiles/NewExample.strings
Binary file not shown.
Binary file modified Tests/Assets/StringsFiles/OldExample.strings
Binary file not shown.
42 changes: 25 additions & 17 deletions Tests/Code/StringsFileUpdaterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class StringsFileUpdaterTests: XCTestCase {

let expectedTranslations = [
("35F-cl-mdI.normalTitle", "Example Button 1", " Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; "),
("COa-YO-eGf.normalTitle", "Example Button 2", " Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; "),
("COa-YO-eGf.normalTitle", "Already Translated", "! Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; "),
("cHL-Zc-L39.normalTitle", "Example Button 3", " Class = \"UIButton\"; normalTitle = \"Example Button 3\"; ObjectID = \"cHL-Zc-L39\"; ")
]

Expand Down Expand Up @@ -76,8 +76,8 @@ class StringsFileUpdaterTests: XCTestCase {
"/* Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",
"\"35F-cl-mdI.normalTitle\" = \"Example Button 1\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Example Button 2\";",
"/*! Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Already Translated\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 3\"; ObjectID = \"cHL-Zc-L39\"; */",
"\"cHL-Zc-L39.normalTitle\" = \"Example Button 3\";",
Expand All @@ -90,17 +90,19 @@ class StringsFileUpdaterTests: XCTestCase {
""
]

XCTAssertEqual(stringsFileUpdater.linesInFile, expectedLinesBeforeIncrementalUpdate)
for (index, expectedLine) in expectedLinesBeforeIncrementalUpdate.enumerate() {
XCTAssertEqual(stringsFileUpdater.linesInFile[index], expectedLine)
}

stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: true)
stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: true, updateCommentWithBase: false)

let expectedLinesAfterIncrementalUpdate = [
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",
"\"35F-cl-mdI.normalTitle\" = \"Example Button 1\";",
"\"35F-cl-mdI.normalTitle\" = \"New Example Button 1\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Example Button 2\";",
"/*! Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Already Translated\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"New Example Button 4\"; ObjectID = \"xyz-12-345\"; */",
"\"xyz-12-345.normalTitle\" = \"\";",
Expand All @@ -110,7 +112,9 @@ class StringsFileUpdaterTests: XCTestCase {
""
]

XCTAssertEqual(stringsFileUpdater.linesInFile, expectedLinesAfterIncrementalUpdate)
for (index, expectedLine) in expectedLinesAfterIncrementalUpdate.enumerate() {
XCTAssertEqual(stringsFileUpdater.linesInFile[index], expectedLine)
}

} catch {
XCTAssertTrue(false, (error as NSError).description)
Expand All @@ -129,8 +133,8 @@ class StringsFileUpdaterTests: XCTestCase {
"/* Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",
"\"35F-cl-mdI.normalTitle\" = \"Example Button 1\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Example Button 2\";",
"/*! Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Already Translated\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 3\"; ObjectID = \"cHL-Zc-L39\"; */",
"\"cHL-Zc-L39.normalTitle\" = \"Example Button 3\";",
Expand All @@ -143,17 +147,19 @@ class StringsFileUpdaterTests: XCTestCase {
""
]

XCTAssertEqual(stringsFileUpdater.linesInFile, expectedLinesBeforeIncrementalUpdate)
for (index, expectedLine) in expectedLinesBeforeIncrementalUpdate.enumerate() {
XCTAssertEqual(stringsFileUpdater.linesInFile[index], expectedLine)
}

stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: false)

let expectedLinesAfterIncrementalUpdate = [
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",
"\"35F-cl-mdI.normalTitle\" = \"Example Button 1\";",
"/* Class = \"UIButton\"; normalTitle = \"New Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",
"\"35F-cl-mdI.normalTitle\" = \"New Example Button 1\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Example Button 2\";",
"/*! Class = \"UIButton\"; normalTitle = \"Example Button 2\"; ObjectID = \"COa-YO-eGf\"; */",
"\"COa-YO-eGf.normalTitle\" = \"Already Translated\";",
"",
"/* Class = \"UIButton\"; normalTitle = \"New Example Button 4\"; ObjectID = \"xyz-12-345\"; */",
"\"xyz-12-345.normalTitle\" = \"New Example Button 4\";",
Expand All @@ -163,7 +169,9 @@ class StringsFileUpdaterTests: XCTestCase {
""
]

XCTAssertEqual(stringsFileUpdater.linesInFile, expectedLinesAfterIncrementalUpdate)
for (index, expectedLine) in expectedLinesAfterIncrementalUpdate.enumerate() {
XCTAssertEqual(stringsFileUpdater.linesInFile[index], expectedLine)
}

} catch {
XCTAssertTrue(false, (error as NSError).description)
Expand Down

0 comments on commit 4bbfd9c

Please sign in to comment.