Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let sweetCookieKitDependency: Package.Dependency =

let package = Package(
name: "CodexBar",
defaultLocalization: "en",
platforms: [
.macOS(.v14),
],
Expand Down Expand Up @@ -98,6 +99,9 @@ let package = Package(
name: "CodexBarWidget",
dependencies: ["CodexBarCore"],
path: "Sources/CodexBarWidget",
resources: [
.process("Resources"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
Expand Down
14 changes: 7 additions & 7 deletions Sources/CodexBar/About.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ func showAbout() {
])
}

let credits = NSMutableAttributedString(string: "Peter SteinbergerMIT License\n")
credits.append(makeLink("GitHub", urlString: "https://github.com/steipete/CodexBar"))
let credits = NSMutableAttributedString(string: "\(AppStrings.tr("© 2025 Peter Steinberger. MIT License."))\n")
credits.append(makeLink(AppStrings.tr("GitHub"), urlString: "https://github.com/steipete/CodexBar"))
credits.append(separator)
credits.append(makeLink("Website", urlString: "https://codexbar.app"))
credits.append(makeLink(AppStrings.tr("Website"), urlString: "https://codexbar.app"))
credits.append(separator)
credits.append(makeLink("Twitter", urlString: "https://twitter.com/steipete"))
credits.append(makeLink(AppStrings.tr("Twitter"), urlString: "https://twitter.com/steipete"))
credits.append(separator)
credits.append(makeLink("Email", urlString: "mailto:peter@steipete.me"))
credits.append(makeLink(AppStrings.tr("Email"), urlString: "mailto:peter@steipete.me"))
if let buildTimestamp, let formatted = formattedBuildTimestamp(buildTimestamp) {
var builtLine = "Built \(formatted)"
var builtLine = AppStrings.fmt("Built %@", formatted)
if let gitCommit, !gitCommit.isEmpty, gitCommit != "unknown" {
builtLine += " (\(gitCommit)"
#if DEBUG
Expand Down Expand Up @@ -68,7 +68,7 @@ private func formattedBuildTimestamp(_ timestamp: String) -> String? {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
formatter.locale = .current
formatter.locale = AppStrings.locale
return formatter.string(from: date)
}

Expand Down
57 changes: 57 additions & 0 deletions Sources/CodexBar/AppLanguage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation

enum AppLanguage: String, CaseIterable, Identifiable {
case system
case english = "en"
case simplifiedChinese = "zh-Hans"
case traditionalChinese = "zh-Hant"

static let userDefaultsKey = "appLanguage"

var id: String {
self.rawValue
}

var localizationIdentifier: String? {
switch self {
case .system:
nil
case .english:
"en"
case .simplifiedChinese:
"zh-Hans"
case .traditionalChinese:
"zh-Hant"
}
}

var locale: Locale {
switch self {
case .system:
.autoupdatingCurrent
case .english:
Locale(identifier: "en")
case .simplifiedChinese:
Locale(identifier: "zh-Hans")
case .traditionalChinese:
Locale(identifier: "zh-Hant")
}
}

var displayName: String {
switch self {
case .system:
"System"
case .english:
"English"
case .simplifiedChinese:
"简体中文"
case .traditionalChinese:
"繁體中文"
}
}

static func resolve(from defaults: UserDefaults) -> AppLanguage {
AppLanguage(rawValue: defaults.string(forKey: self.userDefaultsKey) ?? "") ?? .system
}
}
37 changes: 37 additions & 0 deletions Sources/CodexBar/AppStringResources.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation

enum AppStringResources {
private static let supportedLocalizations = ["en", "zh-Hans", "zh-Hant"]

static func localizedString(for key: String, table: String, language: AppLanguage) -> String {
if let localized = self.localizedString(
for: key,
table: table,
localization: language.localizationIdentifier)
{
return localized
}
if let english = self.localizedString(for: key, table: table, localization: "en") {
return english
}
return key
}

private static func localizedString(for key: String, table: String, localization: String?) -> String? {
guard let bundle = self.bundle(for: localization) else { return nil }
let sentinel = "__codexbar_missing_translation__"
let value = bundle.localizedString(forKey: key, value: sentinel, table: table)
return value == sentinel ? nil : value
}

private static func bundle(for localization: String?) -> Bundle? {
guard let localization else { return Bundle.module }
guard self.supportedLocalizations.contains(localization),
let bundleURL = Bundle.module.resourceURL?.appendingPathComponent("\(localization).lproj"),
FileManager.default.fileExists(atPath: bundleURL.path)
else {
return nil
}
return Bundle(url: bundleURL)
}
}
Loading