Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌐 Improve localization process #499

Merged
merged 4 commits into from
Jul 9, 2024
Merged
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
1,676 changes: 1,022 additions & 654 deletions Loop/Localizable.xcstrings

Large diffs are not rendered by default.

104 changes: 52 additions & 52 deletions Loop/Luminare/Loop/AboutConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import SwiftUI

class AboutConfigurationModel: ObservableObject {
let currentIcon = Defaults[.currentIcon] // no need for didSet since it won't change here
private var shuffledTexts: [LocalizedStringKey] = [] // Store the shuffled texts

@Published var isHoveringOverVersionCopier = false

@Published var updateButtonTitle: LocalizedStringKey = "Check for updates…"
@Published var updateButtonTitle: String = .init(localized: "Check for updates…")

let credits: [CreditItem] = [
.init(
Expand Down Expand Up @@ -58,54 +57,55 @@ class AboutConfigurationModel: ObservableObject {
]

// A max of 28 W's can fit in here :)
var upToDateText: [LocalizedStringKey] = [
"Engage! ...in the current version, it's the latest.",
"This app is more up to date than my diary entries!",
"You're in the clear, no updates in the atmosphere!",
"The odds are ever in your favor, no updates today!",
"Our app is on a digital diet. No new bytes allowed.",
"New version? Sorry, we're too attached to this one.",
"Your Loop is loopier than ever, no updates found!",
"I'm giving it all she's got, Captain! No updates!",
"In a galaxy far, far away... still no updates!",
"You've got the precious, no updates needed!",
"Riding at warp speed, no updates in sight!",
"This is not the update you're looking for!",
"We've misplaced the 'Update' button. Oops!",
"I swear it was here somewhere... one sec",
"An apple a day keeps the... updates away.",
"May the Force be with you... next time!",
"The Force is strong with this version!",
"Just a small town app, same old version",
"Winter is coming. Updates aren't yet.",
"Sweet dreams are made of... no updates",
"The update fairy skipped us this week.",
"Stay sharp, more intel coming soon!",
"You're cruising on the latest tech!",
"We’ll be back. With updates... later",
"A penny for your... lack of updates.",
"You've already got the best Loop!",
"One does not simply update Loop.",
"All work and no... no updates...",
"A watched pot never... updates.",
"99 problems, updates ain't one.",
"I... uhh... one sec I lost it",
"You’ve leveled up to the max!",
"Beggars can't be... updaters.",
"Money can't buy... updates.",
"No new intel, Commander.",
"No updates? Great Scott!",
"No updates, Mr. Anderson",
"No updates in Ba Sing Se",
"Updates? In this economy?",
"Check back next time!",
"Loop is in its prime!",
"All systems are a-go!",
"You're up to date :)",
"No updates yet!"
var upToDateText: [String] = [
.init(localized: "No updates available message 1", defaultValue: "Engage! in the current version, it's the latest."),
.init(localized: "No updates available message 2", defaultValue: "This app is more up to date than my diary entries!"),
.init(localized: "No updates available message 3", defaultValue: "You're in the clear, no updates in the atmosphere!"),
.init(localized: "No updates available message 4", defaultValue: "The odds are ever in your favor, no updates today!"),
.init(localized: "No updates available message 5", defaultValue: "Our app is on a digital diet. No new bytes allowed."),
.init(localized: "No updates available message 6", defaultValue: "New version? Sorry, we're too attached to this one."),
.init(localized: "No updates available message 7", defaultValue: "Your Loop is Loopier than ever, no updates found!"),
.init(localized: "No updates available message 8", defaultValue: "I'm giving it all she's got, Captain! No updates!"),
.init(localized: "No updates available message 9", defaultValue: "In a galaxy far, far away still no updates!"),
.init(localized: "No updates available message 10", defaultValue: "You've got the precious, no updates needed!"),
.init(localized: "No updates available message 11", defaultValue: "Riding at warp speed, no updates in sight!"),
.init(localized: "No updates available message 12", defaultValue: "This is not the update you're looking for!"),
.init(localized: "No updates available message 13", defaultValue: "We've misplaced the 'Update' button. Oops!"),
.init(localized: "No updates available message 14", defaultValue: "I swear it was here somewhere one sec"),
.init(localized: "No updates available message 15", defaultValue: "An apple a day keeps the updates away."),
.init(localized: "No updates available message 16", defaultValue: "May the Force be with you next time!"),
.init(localized: "No updates available message 17", defaultValue: "The Force is strong with this version!"),
.init(localized: "No updates available message 18", defaultValue: "Just a small town app, same old version"),
.init(localized: "No updates available message 19", defaultValue: "Winter is coming. Updates aren't yet."),
.init(localized: "No updates available message 20", defaultValue: "Sweet dreams are made of no updates"),
.init(localized: "No updates available message 21", defaultValue: "The update fairy skipped us this week."),
.init(localized: "No updates available message 22", defaultValue: "Stay sharp, more intel coming soon!"),
.init(localized: "No updates available message 23", defaultValue: "You're cruising on the latest tech!"),
.init(localized: "No updates available message 24", defaultValue: "We’ll be back. With updates later"),
.init(localized: "No updates available message 25", defaultValue: "A penny for your lack of updates."),
.init(localized: "No updates available message 26", defaultValue: "You've already got the best Loop!"),
.init(localized: "No updates available message 27", defaultValue: "One does not simply update Loop."),
.init(localized: "No updates available message 28", defaultValue: "All work and no no updates…"),
.init(localized: "No updates available message 29", defaultValue: "A watched pot never updates."),
.init(localized: "No updates available message 30", defaultValue: "99 problems, updates ain't one."),
.init(localized: "No updates available message 31", defaultValue: "I… uhh one sec I lost it"),
.init(localized: "No updates available message 32", defaultValue: "You’ve leveled up to the max!"),
.init(localized: "No updates available message 33", defaultValue: "Beggars can't be updaters."),
.init(localized: "No updates available message 34", defaultValue: "Money can't buy updates."),
.init(localized: "No updates available message 35", defaultValue: "No new intel, Commander."),
.init(localized: "No updates available message 36", defaultValue: "No updates? Great Scott!"),
.init(localized: "No updates available message 37", defaultValue: "No updates, Mr. Anderson"),
.init(localized: "No updates available message 38", defaultValue: "No updates in Ba Sing Se"),
.init(localized: "No updates available message 39", defaultValue: "Updates? In this economy?"),
.init(localized: "No updates available message 40", defaultValue: "Check back next time!"),
.init(localized: "No updates available message 41", defaultValue: "Loop is in its prime!"),
.init(localized: "No updates available message 42", defaultValue: "All systems are a-go!"),
.init(localized: "No updates available message 43", defaultValue: "You're up to date :)"),
.init(localized: "No updates available message 44", defaultValue: "No updates yet!")
]
private var shuffledTexts: [String] = []

func getNextUpToDateText() -> LocalizedStringKey {
func getNextUpToDateText() -> String {
// If shuffledTexts is empty, fill it with a shuffled version of upToDateText
if shuffledTexts.isEmpty {
shuffledTexts = upToDateText.shuffled()
Expand Down Expand Up @@ -166,7 +166,7 @@ struct AboutConfigurationView: View {
Text(
model.isHoveringOverVersionCopier
? "Version \(Bundle.main.appVersion ?? "Unknown") (\(Bundle.main.appBuild ?? 0))"
: (timesLooped >= 1_000_000 ? "You've looped... uhh... I... lost count..." : "You've looped \(timesLooped) times!")
: (timesLooped >= 1_000_000 ? "You've looped uhh… I… lost count" : "You've looped \(timesLooped) times!")
)
.contentTransition(.numericText(countsDown: !model.isHoveringOverVersionCopier))
.animation(LuminareSettingsWindow.animation, value: model.isHoveringOverVersionCopier)
Expand Down Expand Up @@ -200,7 +200,7 @@ struct AboutConfigurationView: View {
let currentTitle = model.updateButtonTitle
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
if model.updateButtonTitle == currentTitle {
model.updateButtonTitle = "Check for updates…"
model.updateButtonTitle = .init(localized: "Check for updates…")
}
}
}
Expand All @@ -212,12 +212,12 @@ struct AboutConfigurationView: View {
}
.onAppear {
if updater.updateState == .available {
model.updateButtonTitle = "Update…"
model.updateButtonTitle = .init(localized: "Update…")
}
}
.onChange(of: updater.updateState) { _ in
if updater.updateState == .available {
model.updateButtonTitle = "Update…"
model.updateButtonTitle = .init(localized: "Update…")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct BehaviorConfigurationView: View {
LuminareToggle("Include padding", isOn: $model.enablePadding)

if model.enablePadding {
Button("Configure padding...") {
Button("Configure padding") {
model.isPaddingConfigurationViewPresented = true
}
.luminareModal(isPresented: $model.isPaddingConfigurationViewPresented) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ struct CustomActionConfigurationView: View {
sliderRange: action.unit == .percentage ?
0...100 :
0...Double(screenSize.width),
suffix: action.unit?.suffix,
suffix: .init(action.unit?.suffix ?? CustomWindowActionUnit.percentage.suffix),
lowerClamp: true
)

Expand All @@ -263,7 +263,7 @@ struct CustomActionConfigurationView: View {
sliderRange: action.unit == .percentage ?
0...100 :
0...Double(screenSize.height),
suffix: action.unit?.suffix,
suffix: .init(action.unit?.suffix ?? CustomWindowActionUnit.percentage.suffix),
lowerClamp: true
)
}
Expand Down Expand Up @@ -308,7 +308,7 @@ struct CustomActionConfigurationView: View {
sliderRange: action.unit == .percentage ?
0...100 :
0...Double(screenSize.width),
suffix: action.unit?.suffix,
suffix: .init(action.unit?.suffix ?? CustomWindowActionUnit.percentage.suffix),
lowerClamp: true
)

Expand All @@ -325,7 +325,7 @@ struct CustomActionConfigurationView: View {
sliderRange: action.unit == .percentage ?
0...100 :
0...Double(screenSize.width),
suffix: action.unit?.suffix,
suffix: .init(action.unit?.suffix ?? CustomWindowActionUnit.percentage.suffix),
lowerClamp: true
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct Keycorder: View {
@State private var eventMonitor: NSEventMonitor?
@State private var shouldShake: Bool = false
@State private var shouldError: Bool = false
@State private var errorMessage: Text = .init("") // We use Text here for String interpolation with images
@State private var errorMessage: LocalizedStringKey = .init(String("")) // We use Text here for String interpolation with images

@State private var isHovering: Bool = false
@State private var isActive: Bool = false
Expand Down Expand Up @@ -65,7 +65,7 @@ struct Keycorder: View {
.modifier(ShakeEffect(shakes: shouldShake ? 2 : 0))
.animation(Animation.default, value: shouldShake)
.popover(isPresented: $shouldError, arrowEdge: .bottom) {
errorMessage
Text(errorMessage)
.multilineTextAlignment(.center)
.padding(8)
}
Expand Down Expand Up @@ -98,9 +98,9 @@ struct Keycorder: View {
selectionKeybind.insert(event.keyCode.baseModifier)
} else {
if let systemImage = event.keyCode.baseModifier.systemImage {
errorMessage = Text("\(Image(systemName: systemImage)) is already used as your trigger key.")
errorMessage = "\(Image(systemName: systemImage)) is already used as your trigger key."
} else {
errorMessage = Text("That key is already used as your trigger key.")
errorMessage = "That key is already used as your trigger key."
}

shouldShake.toggle()
Expand All @@ -121,9 +121,7 @@ struct Keycorder: View {
}

if (selectionKeybind.count + triggerKey.count) >= keyLimit {
errorMessage = Text(
"You can only use up to \(keyLimit) keys in a keybind, including the trigger key."
)
errorMessage = "You can only use up to \(keyLimit) keys in a keybind, including the trigger key."
shouldShake.toggle()
shouldError = true
} else {
Expand Down Expand Up @@ -151,14 +149,12 @@ struct Keycorder: View {
willSet = false
if keybind.direction == .custom {
if let name = keybind.name {
self.errorMessage = Text("That keybind is already being used by \(name).")
self.errorMessage = "That keybind is already being used by \(name)."
} else {
self.errorMessage = Text("That keybind is already being used by another custom keybind.")
self.errorMessage = "That keybind is already being used by another custom keybind."
}
} else {
self.errorMessage = Text(
"That keybind is already being used by \(keybind.direction.name.lowercased())."
)
self.errorMessage = "That keybind is already being used by \(keybind.direction.name.lowercased())."
}
self.shouldShake.toggle()
self.shouldError = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct KeybindingsConfigurationView: View {
"Trigger delay",
value: $model.triggerDelay,
sliderRange: 0...1,
suffix: .init(.init(localized: "Seconds", defaultValue: "s")),
suffix: .init(.init(localized: "Measurement unit: seconds", defaultValue: "s")),
step: 0.1,
lowerClamp: true,
decimalPlaces: 1
Expand Down
85 changes: 50 additions & 35 deletions Loop/Luminare/Theming/AccentColorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import Defaults
import Luminare
import SwiftUI

// MARK: - Model

class AccentColorConfigurationModel: ObservableObject {
// MARK: - Defaults
// MARK: Defaults

@Published var useSystemAccentColor = Defaults[.useSystemAccentColor] {
didSet { Defaults[.useSystemAccentColor] = useSystemAccentColor }
Expand Down Expand Up @@ -38,6 +40,26 @@ class AccentColorConfigurationModel: ObservableObject {
}
}

// MARK: Color mode helpers

var isCustom: Bool {
useSystemAccentColor ? false : !processWallpaper
}

var isWallpaper: Bool {
processWallpaper && !useSystemAccentColor
}

var accentColorOption: AccentColorOption {
get {
useSystemAccentColor ? .system : (processWallpaper ? .wallpaper : .custom)
}
set {
useSystemAccentColor = newValue == .system
processWallpaper = newValue == .wallpaper
}
}

func syncWallpaper() {
Task {
await WallpaperProcessor.fetchLatestWallpaperColors()
Expand All @@ -57,6 +79,30 @@ class AccentColorConfigurationModel: ObservableObject {
}
}

// MARK: - AccentColorOption

enum AccentColorOption: CaseIterable {
case system
case wallpaper
case custom

var image: Image {
switch self {
case .system: Image(systemName: "apple.logo")
case .wallpaper: Image(._18PxImageDepth)
case .custom: Image(._18PxColorPalette)
}
}

var text: String {
switch self {
case .system: .init(localized: "Accent color option: System", defaultValue: "System")
case .wallpaper: .init(localized: "Accent color option: Wallpaper", defaultValue: "Wallpaper")
case .custom: .init(localized: "Accent color option: Custom", defaultValue: "Custom")
}
}
}

// MARK: - View

struct AccentColorConfigurationView: View {
Expand All @@ -65,15 +111,15 @@ struct AccentColorConfigurationView: View {
var body: some View {
LuminareSection {
LuminarePicker(
elements: ["System", "Wallpaper", "Custom"],
elements: AccentColorOption.allCases,
selection: $model.accentColorOption.animation(LuminareSettingsWindow.animation),
columns: 3,
roundBottom: model.useSystemAccentColor
) { option in
VStack(spacing: 6) {
Spacer()
model.image(for: option)
Text(option)
option.image
Text(option.text)
Spacer()
}
.font(.title3)
Expand Down Expand Up @@ -115,34 +161,3 @@ struct AccentColorConfigurationView: View {
}
}
}

// MARK: - View Extension

extension AccentColorConfigurationModel {
var isCustom: Bool {
useSystemAccentColor ? false : !processWallpaper
}

var isWallpaper: Bool {
processWallpaper && !useSystemAccentColor
}

var accentColorOption: String {
get {
useSystemAccentColor ? "System" : (processWallpaper ? "Wallpaper" : "Custom")
}
set {
useSystemAccentColor = newValue == "System"
processWallpaper = newValue == "Wallpaper"
}
}

func image(for option: String) -> Image {
let imageNames = [
"System": Image(systemName: "apple.logo"),
"Wallpaper": Image(._18PxImageDepth),
"Custom": Image(._18PxColorPalette)
]
return imageNames[option] ?? Image(systemName: "exclamationmark.triangle")
}
}
Loading
Loading