Skip to content

Commit

Permalink
initial feature implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Leo Liu committed Nov 7, 2023
1 parent 29042ca commit d6d8805
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 17 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,9 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# VIM
.*.sw*

# macOS
.DS_Store
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,19 @@ If you are not familiar with [Apple's String Catalog](https://developer.apple.co
- If you are dealing with an old base and needs to provide localization right now, add copies in the String Catalog. Otherwise leave them along. We'll revisit it later.
- Whenever you want to add a new string to the project, always manually create an entry in `Localizable.xcstrings`.
- Use a variable name friendly string as `Key`.
- TODO: plural forms etc. is out of the scope for now.
- TODO: plural forms etc. is to be added later.
- Create `enum` in your code that contains all the String Catalog keys.
- It can be constructed manually, all you need is to use a `LocalizedStringKey` in `SwiftUI.View`, not the String itself.
- Helper function: `var key: LocalizedStringKey { LocalizedStringKey(rawValue) }`
- OR use the [SringCatalogEnum](./StringCatalogEnum) CLI tool in this repo.
- Copy `xcstrings-enum-generate` to your project.
- Added something like `xcstrings-enum-generate --xcstrings-path ../Resources/Localizable.xcstrings --output-filename ../Generated/XcodeString.swift` to generate the `enum`.
- OR use [SwiftGen](https://github.com/SwiftGen/SwiftGen/issues/1065) - hopefully better support will be added in future.
- OR explore how to achieve it with [Sourcery](https://github.com/krzysztofzablocki/Sourcery).
- Use `LocalizedStringKey(key)` in your SwiftUI components.
- With `StringCatalogEnum`: `Text(XCS.welcomeBack.key)`
- At this point, you end up with a native `enum`, hopefully generated from your `String Catalog`.
- With `StringCatalogEnum`: remove hard-coded strings from your `SwiftUI` components and create manual keys and localized strings.

## Appendix

Expand Down Expand Up @@ -68,4 +78,8 @@ I think this is because Apple, at its core, is a company that sells products. It

Companies like Google, on the other hand, are built around technology. They don't hesitate to kill their services and update their tech stack, which sometimes results confusion and frustration. But they do offer more software architectural level of thinking, which can be insightful for developers in their ecosystems.

Of course, this is just a high level summary. Apple built async/wait APIs to promote better codes, and Google still uses XML (bah) for string resources. And just like iOS and Android, we are seeing both parties learn from each other. Hopefully technology will keep evolving.
Of course, this is just a high level summary. Apple built async/wait APIs to promote better codes, and Google still uses XML (bah) for string resources. And just like iOS and Android, we are seeing both parties learn from each other. Hopefully technology will keep evolving.

## Credit

- CLI tool built from [SwiftCLITemplate](https://github.com/superarts/SwiftCLITemplate).
5 changes: 3 additions & 2 deletions SringCatalogEnum/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ NAME=xcstrings-enum-generate
all: debug
./$(NAME) \
--xcstrings-path ~/prj/ios/quible/Quible/Resources/Localizable.xcstrings \
--enum-name XcodeStrings \
--enum-type-alias X
--enum-name XcodeString \
--enum-typealias X \
--output-filename ~/prj/ios/quible/Quible/Const/Generated/XcodeString.swift

debug:
swift build
Expand Down
69 changes: 56 additions & 13 deletions SringCatalogEnum/Sources/SringCatalogEnum/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ struct SringCatalogEnum: ParsableCommand {
case unexpectedJSON(message: String? = nil)
}

enum Keyword: String, CaseIterable {
case `continue`, `default`
}

// @Argument(help: "The phrase to repeat.")
// var phrase: String

Expand All @@ -24,11 +28,14 @@ struct SringCatalogEnum: ParsableCommand {
@Option(name: .long, help: "Full path and filename of the 'xcstrings' file.")
var xcstringsPath: String

@Option(name: .long, help: "Full path and filename of the generated Swift file.")
var outputFilename: String

@Option(name: .long, help: "Generated enum name.")
var enumName: String = "XcodeString"
var enumName: String = "XcodeStringKey"

@Option(name: .long, help: "A typealias of the generated enum name.")
var enumTypeAlias: String = "XCS"
var enumTypealias: String = "XCS"

func run() throws {
print("LOADING: \(xcstringsPath)")
Expand All @@ -49,18 +56,22 @@ struct SringCatalogEnum: ParsableCommand {
// print(strings)

var output = """
// This file is generated by XcodeStringEnum. Please do *NOT* update it manually.
// This file is generated by XcodeStringEnum. Please do *NOT* update it manually.
// As a common practice, swiftLint is disabled for generated files.
// swiftlint:disable all
import SwiftUI
import SwiftUI
/// Makes it a bit easier to type.
typealias \(enumTypeAlias) = \(enumName)
/// Makes it a bit easier to type.
typealias \(enumTypealias) = \(enumName)
enum \(enumName): String, CaseIterable {
/// Generated by SringCatalogEnum, this enum contains all existing Strin Category keys.
enum \(enumName): String, CaseIterable {
"""
"""

var cases = [String]()
var knownCases = [String]()
for (key, _) in strings {
guard let name = convertToVariableName(key) else {
print("SKIPPING: \(key)")
Expand All @@ -69,10 +80,19 @@ struct SringCatalogEnum: ParsableCommand {
guard key == name else {
continue
}
guard !knownCases.contains(name) else {
cases.append(" // TODO: fix duplicated entry - case \(name)\n")
continue
}
knownCases.append(name)

// print("\(name):\t\(key)")
// TODO: extract `localizations.en.stringUnit.value` and add in comments as inline documents
cases.append(" case \(name)\n")
if Keyword.allCases.map({ $0.rawValue }).contains(name) {
cases.append(" case `\(name)`\n")
} else {
cases.append(" case \(name)\n")
}
}
cases.sort()
cases.forEach { string in
Expand All @@ -81,10 +101,10 @@ struct SringCatalogEnum: ParsableCommand {

output += """
// MARK: - The following cases should be manually replaced in your codebase.
// MARK: - The following cases should be manually replaced in your codebase.
"""
"""
cases.removeAll()
for (key, _) in strings {
guard let name = convertToVariableName(key) else {
Expand All @@ -94,18 +114,41 @@ struct SringCatalogEnum: ParsableCommand {
guard key != name else {
continue
}
guard !knownCases.contains(name) else {
cases.append(" // TODO: fix duplicated entry - case \(name)\n")
continue
}
knownCases.append(name)

// print("\(name):\t\(key)")
// TODO: probably missing " handling?
cases.append(" case \(name) = \"\(key.replacingOccurrences(of: "\n", with: "")))\"\n")
if Keyword.allCases.map({ $0.rawValue }).contains(name) {
cases.append(" case `\(name)` = \"\(key.replacingOccurrences(of: "\n", with: ""))\"\n")
} else {
cases.append(" case \(name) = \"\(key.replacingOccurrences(of: "\n", with: ""))\"\n")
}
}
// cases = Array(Set<String>(cases))
cases.sort()
cases.forEach { string in
output += string
}

output += "}"
output += """
/// Usage: `SwiftUI.Text(\(enumTypealias).yourStringCatalogKey.key)`
var key: LocalizedStringKey { LocalizedStringKey(rawValue) }
// var text: String { String(localized: key) }
// var text: String.LocalizationValue { String.LocalizationValue(rawValue) }
}
// swiftlint:enable all
"""
print(output)
let outputURL = URL(fileURLWithPath: outputFilename)
try output.write(to: outputURL, atomically: true, encoding: .utf8)
print("Written to: \(outputFilename)")
}

// TODO: add to some StringUtility
Expand Down

0 comments on commit d6d8805

Please sign in to comment.