Skip to content

Commit

Permalink
Merge pull request #16 from nearfri/fix-xcstringkey-gen
Browse files Browse the repository at this point in the history
Fix bug that generate invalid member identifier when key has punctuation
  • Loading branch information
nearfri committed Apr 12, 2024
2 parents 7324588 + 10945fb commit 7bbe274
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 23 deletions.
Expand Up @@ -30,7 +30,7 @@
{
"commandName": "xcstrings2swift",
"catalogPath": "Sources/Resource/Resources/Localizable.xcstrings",
"bundle": "for-class:BundleFinder.self",
"bundle": "at-url:Bundle.module.bundleURL",
"swiftPath": "Sources/Resource/Keys/LocalizedStringResource+.swift"
}
]
Expand Down
@@ -1,8 +1,6 @@
import Foundation

private class BundleFinder {}

package extension LocalizedStringResource {
public extension LocalizedStringResource {
/// \"\\(fileName)\" will be deleted.\
/// This action cannot be undone.
static func alert_delete_file(named fileName: String) -> Self {
Expand All @@ -11,126 +9,126 @@ package extension LocalizedStringResource {
\"\(fileName)\" will be deleted.
This action cannot be undone.
""",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Cancel
static var common_cancel: Self {
.init("common_cancel",
defaultValue: "Cancel",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Confirm
static var common_confirm: Self {
.init("common_confirm",
defaultValue: "Confirm",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Done
static var common_done: Self {
.init("common_done",
defaultValue: "Done",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// \\(dogName) ate \\(appleCount) today!
static func dog_eating_apples(dogName: String, appleCount: Int) -> Self {
.init("dog_eating_apples",
defaultValue: "\(dogName) ate \(appleCount) today!",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Copy
static var editMenu_copy: Self {
.init("editMenu_copy",
defaultValue: "Copy",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Cut
static var editMenu_cut: Self {
.init("editMenu_cut",
defaultValue: "Cut",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Paste
static var editMenu_paste: Self {
.init("editMenu_paste",
defaultValue: "Paste",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Redo \\(command)
static func editMenu_redo(command: String) -> Self {
.init("editMenu_redo",
defaultValue: "Redo \(command)",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Text Style
static var editMenu_textStyle: Self {
.init("editMenu_textStyle",
defaultValue: "Text Style",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Undo \\(command)
static func editMenu_undo(command: String) -> Self {
.init("editMenu_undo",
defaultValue: "Undo \(command)",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// \\(fileCount) files
static func fileList_fileCount(_ fileCount: Int) -> Self {
.init("fileList_fileCount",
defaultValue: "\(fileCount) files",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

// xcresource:use-raw
/// 100% success
static var success100: Self {
.init("success100",
defaultValue: "100% success",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Bold
static var text_bold: Self {
.init("text_bold",
defaultValue: "Bold",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Italic
static var text_italic: Self {
.init("text_italic",
defaultValue: "Italic",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Strikethrough
static var text_strikethrough: Self {
.init("text_strikethrough",
defaultValue: "Strikethrough",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Underline
static var text_underline: Self {
.init("text_underline",
defaultValue: "Underline",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}

/// Tap here
static var user_instructions: Self {
.init("user_instructions",
defaultValue: "Tap here",
bundle: .forClass(BundleFinder.self))
bundle: .atURL(Bundle.module.bundleURL))
}
}
32 changes: 32 additions & 0 deletions Sources/LocStringResourceGen/LocalizationItem.swift
Expand Up @@ -13,6 +13,38 @@ extension LocalizationItem {
return id
}
}

public func fixingID() -> MemberDeclation {
if id.isEmpty { return self }

var newID = ""

let firstChar = id[id.startIndex]
if isIdentifierHead(firstChar) {
newID = String(firstChar)
} else {
newID = firstChar.isNumber ? "_\(firstChar)" : "_"
}

for char in id.dropFirst() {
newID.append(isIdentifierBody(char) ? char : "_")
}

switch self {
case .property:
return .property(newID)
case .method(_, let parameters):
return .method(newID, parameters)
}
}

private func isIdentifierHead(_ character: Character) -> Bool {
return character.isLetter || character == "_"
}

private func isIdentifierBody(_ character: Character) -> Bool {
return isIdentifierHead(character) || character.isNumber
}
}

public struct Parameter: Hashable {
Expand Down
Expand Up @@ -70,7 +70,7 @@ struct StringCatalogDTOMapper {
key: key,
defaultValue: defaultValue,
rawDefaultValue: stringUnitDTO.escapedValue,
memberDeclation: memberDeclation)
memberDeclation: memberDeclation.fixingID())
}

private func stringUnitDTO(from dto: LocalizationDTO) -> StringUnitDTO? {
Expand Down
48 changes: 48 additions & 0 deletions Tests/LocStringResourceGenTests/LocalizationItemTests.swift
@@ -1,6 +1,54 @@
import XCTest
@testable import LocStringResourceGen

final class LocalizationItemMemberDeclationTests: XCTestCase {
func test_fixingID_validPropertyID_returnsEqual() throws {
// Given
let sut = LocalizationItem.MemberDeclation.property("valid_key")

// When
let fixed = sut.fixingID()

// Then
XCTAssertEqual(fixed, sut)
}

func test_fixingID_validMethodID_returnsEqual() throws {
// Given
let sut = LocalizationItem.MemberDeclation.method(
"valid_id",
[.init(firstName: "p1", type: "Int")])

// When
let fixed = sut.fixingID()

// Then
XCTAssertEqual(fixed, sut)
}

func test_fixingID_invalidID_returnFixed() throws {
// Given
let sut = LocalizationItem.MemberDeclation.property("punctuation/key")

// When
let fixed = sut.fixingID()

// Then
XCTAssertEqual(fixed, .property("punctuation_key"))
}

func test_fixingID_idStartsWithNumber_returnFixed() throws {
// Given
let sut = LocalizationItem.MemberDeclation.property("1number_key")

// When
let fixed = sut.fixingID()

// Then
XCTAssertEqual(fixed, .property("_1number_key"))
}
}

final class LocalizationItemTests: XCTestCase {
func test_documentComments() throws {
func test(defaultValue: String,
Expand Down

0 comments on commit 7bbe274

Please sign in to comment.