diff --git a/Rectangle.xcodeproj/project.pbxproj b/Rectangle.xcodeproj/project.pbxproj index a8ec95c2..ad05e4bf 100644 --- a/Rectangle.xcodeproj/project.pbxproj +++ b/Rectangle.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 6490B39F27BF98840056C220 /* BottomCenterRightEighthCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6490B39E27BF98840056C220 /* BottomCenterRightEighthCalculation.swift */; }; 6490B3A127BF98C70056C220 /* BottomRightEighthCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6490B3A027BF98C70056C220 /* BottomRightEighthCalculation.swift */; }; 729E0A982AFF76B1006E2F48 /* CenterProminentlyCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 729E0A972AFF76B1006E2F48 /* CenterProminentlyCalculation.swift */; }; + 7BE578EF2C5BF4EE0083DAE3 /* CycleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */; }; 866661F2257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866661F1257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift */; }; 94E9B08E2C3B8D97004C7F41 /* MacTilingDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E9B08D2C3B8D97004C7F41 /* MacTilingDefaults.swift */; }; 94E9B0902C3E4578004C7F41 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E9B08F2C3E4578004C7F41 /* StringExtension.swift */; }; @@ -186,6 +187,7 @@ 6490B39E27BF98840056C220 /* BottomCenterRightEighthCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomCenterRightEighthCalculation.swift; sourceTree = ""; }; 6490B3A027BF98C70056C220 /* BottomRightEighthCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomRightEighthCalculation.swift; sourceTree = ""; }; 729E0A972AFF76B1006E2F48 /* CenterProminentlyCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenterProminentlyCalculation.swift; sourceTree = ""; }; + 7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CycleSize.swift; sourceTree = ""; }; 866661F1257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatedExecutionsInThirdsCalculation.swift; sourceTree = ""; }; 94E9B08D2C3B8D97004C7F41 /* MacTilingDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacTilingDefaults.swift; sourceTree = ""; }; 94E9B08F2C3E4578004C7F41 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; @@ -552,6 +554,7 @@ 9821405F22B3EFB200ABFB3F /* Defaults.swift */, 984EDB0E29A42ED200D119D2 /* LaunchOnLogin.swift */, 98C1008B2305F1FA006E5344 /* SubsequentExecutionMode.swift */, + 7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */, 985B9BF422B93EEC00A2E8F0 /* ApplicationToggle.swift */, 9824703022AFA8470037B409 /* RectangleStatusItem.swift */, 9824703622B0F3200037B409 /* WindowAction.swift */, @@ -922,6 +925,7 @@ 9824703722B0F3200037B409 /* WindowAction.swift in Sources */, B4521F932BD7CEFB00FD43CC /* ChangeWindowDimensionCalculation.swift in Sources */, 9821402922B3889100ABFB3F /* LowerLeftCalculation.swift in Sources */, + 7BE578EF2C5BF4EE0083DAE3 /* CycleSize.swift in Sources */, 9821402122B3884600ABFB3F /* BottomHalfCalculation.swift in Sources */, 98910B42231476B30066EC23 /* PrefsViewController.swift in Sources */, 9851A5C3251BEBA300ECF78C /* OrientationAware.swift in Sources */, diff --git a/Rectangle/Base.lproj/Main.storyboard b/Rectangle/Base.lproj/Main.storyboard index 026f8243..4ae20109 100644 --- a/Rectangle/Base.lproj/Main.storyboard +++ b/Rectangle/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -337,7 +337,7 @@ - + @@ -386,7 +386,7 @@ - + @@ -435,7 +435,7 @@ - + @@ -484,7 +484,7 @@ - + @@ -536,7 +536,7 @@ - + @@ -595,7 +595,7 @@ - + @@ -647,7 +647,7 @@ - + @@ -699,7 +699,7 @@ - + @@ -751,7 +751,7 @@ - + @@ -832,7 +832,7 @@ - + @@ -881,7 +881,7 @@ - + @@ -930,7 +930,7 @@ - + @@ -979,7 +979,7 @@ - + @@ -1028,7 +1028,7 @@ - + @@ -1077,7 +1077,7 @@ - + @@ -1126,7 +1126,7 @@ - + @@ -1182,7 +1182,7 @@ - + @@ -1231,7 +1231,7 @@ - + @@ -1351,7 +1351,7 @@ - + @@ -1400,7 +1400,7 @@ - + @@ -1449,7 +1449,7 @@ - + @@ -1498,7 +1498,7 @@ - + @@ -1547,7 +1547,7 @@ - + @@ -1603,7 +1603,7 @@ - + @@ -1652,7 +1652,7 @@ - + @@ -1701,7 +1701,7 @@ - + @@ -1750,7 +1750,7 @@ - + @@ -1799,7 +1799,7 @@ - + @@ -1848,7 +1848,7 @@ - + @@ -1930,7 +1930,7 @@ - + @@ -1979,7 +1979,7 @@ - + @@ -2028,7 +2028,7 @@ - + @@ -2077,7 +2077,7 @@ - + @@ -2133,7 +2133,7 @@ - + @@ -2182,7 +2182,7 @@ - + @@ -2231,7 +2231,7 @@ - + @@ -2280,7 +2280,7 @@ - + @@ -2329,7 +2329,7 @@ - + @@ -2378,7 +2378,7 @@ - + @@ -2569,14 +2569,14 @@ - + - - + + - + - + @@ -2613,7 +2613,7 @@ - - + + @@ -2631,7 +2631,7 @@ - + - + - + - + @@ -3305,7 +3328,7 @@ DQ - + @@ -3366,7 +3389,7 @@ DQ - + @@ -3382,7 +3405,7 @@ DQ - + @@ -3393,7 +3416,7 @@ DQ - + @@ -3401,7 +3424,7 @@ DQ - + @@ -3411,7 +3434,7 @@ DQ - + @@ -3431,7 +3454,7 @@ DQ - + @@ -3528,7 +3551,7 @@ DQ - + @@ -3536,7 +3559,7 @@ DQ - + @@ -3584,7 +3607,7 @@ DQ - + @@ -3592,7 +3615,7 @@ DQ - + diff --git a/Rectangle/CycleSize.swift b/Rectangle/CycleSize.swift new file mode 100644 index 00000000..9a912e81 --- /dev/null +++ b/Rectangle/CycleSize.swift @@ -0,0 +1,130 @@ +// +// CycleSize.swift +// Rectangle +// +// Created by Eskil Gjerde Sviggum on 01/08/2024. +// Copyright © 2024 Ryan Hanson. All rights reserved. +// + +import Foundation + +enum CycleSize: Int, CaseIterable { + case twoThirds = 0 + case oneHalf = 1 + case oneThird = 2 + case oneQuarter = 3 + case threeQuarters = 4 + + static func fromBits(bits: Int) -> Set { + Set( + Self.allCases.filter { + (bits >> $0.rawValue) & 1 == 1 + } + ) + } + + static var firstSize = CycleSize.oneHalf + static var defaultSizes: Set = [.oneHalf, .twoThirds, .oneThird] + + // The expected order of the cycle sizes is to start with the + // first division, then go gradually upwards in size and wrap + // around to the smaller sizes. + // + // For example if all cycles are used, the order should be: + // 1/2, 2/3, 3/4, 1/4, 1/3 + static var sortedSizes: [CycleSize] = { + let sortedSizes = Self.allCases.sorted(by: { $0.fraction < $1.fraction }) + + guard let firstSizeIndex = sortedSizes.firstIndex(of: firstSize) else { + return sortedSizes + } + + let lessThanFistSizes = sortedSizes[0.. Int { + var bits = 0 + self.forEach { + bits |= 1 << $0.rawValue + } + return bits + } +} + +class CycleSizesDefault: Default { + public private(set) var key: String = "selectedCycleSizes" + private var initialized = false + + var value: Set { + didSet { + if initialized { + UserDefaults.standard.set(value.toBits(), forKey: key) + } + } + } + + init() { + let bits = UserDefaults.standard.integer(forKey: key) + value = CycleSize.fromBits(bits: bits) + initialized = true + } + + func load(from codable: CodableDefault) { + if let bits = codable.int { + let divisions = CycleSize.fromBits(bits: bits) + value = divisions + } + } + + func toCodable() -> CodableDefault { + return CodableDefault(int: value.toBits()) + } + +} diff --git a/Rectangle/Defaults.swift b/Rectangle/Defaults.swift index b1628a1e..13bd2954 100644 --- a/Rectangle/Defaults.swift +++ b/Rectangle/Defaults.swift @@ -14,6 +14,8 @@ class Defaults { static let hideMenuBarIcon = BoolDefault(key: "hideMenubarIcon") static let alternateDefaultShortcuts = BoolDefault(key: "alternateDefaultShortcuts") // switch to magnet defaults static let subsequentExecutionMode = SubsequentExecutionDefault() + static let selectedCycleSizes = CycleSizesDefault() + static let cycleSizesIsChanged = BoolDefault(key: "cycleSizesIsChanged") static let allowAnyShortcut = BoolDefault(key: "allowAnyShortcut") static let windowSnapping = OptionalBoolDefault(key: "windowSnapping") static let almostMaximizeHeight = FloatDefault(key: "almostMaximizeHeight") @@ -95,6 +97,8 @@ class Defaults { hideMenuBarIcon, alternateDefaultShortcuts, subsequentExecutionMode, + selectedCycleSizes, + cycleSizesIsChanged, allowAnyShortcut, windowSnapping, almostMaximizeHeight, diff --git a/Rectangle/PrefsWindow/SettingsViewController.swift b/Rectangle/PrefsWindow/SettingsViewController.swift index 376bd804..c9065e1c 100644 --- a/Rectangle/PrefsWindow/SettingsViewController.swift +++ b/Rectangle/PrefsWindow/SettingsViewController.swift @@ -34,8 +34,17 @@ class SettingsViewController: NSViewController { @IBOutlet weak var stageSlider: NSSlider! @IBOutlet weak var stageLabel: NSTextField! + @IBOutlet weak var cycleSizesView: NSStackView! + + @IBOutlet var cycleSizesViewHeightConstraint: NSLayoutConstraint! + + @IBOutlet var todoViewHeightConstraint: NSLayoutConstraint! + + private var aboutTodoWindowController: NSWindowController? + private var cycleSizeCheckboxes = [NSButton]() + @IBAction func toggleLaunchOnLogin(_ sender: NSButton) { let newSetting: Bool = sender.state == .on if #available(macOS 13, *) { @@ -64,6 +73,7 @@ class SettingsViewController: NSViewController { } Defaults.subsequentExecutionMode.value = mode + initializeCycleSizesView(animated: true) } @IBAction func gapSliderChanged(_ sender: NSSlider) { @@ -123,7 +133,7 @@ class SettingsViewController: NSViewController { @IBAction func toggleTodoMode(_ sender: NSButton) { let newSetting: Bool = sender.state == .on Defaults.todo.enabled = newSetting - showHideTodoModeSettings() + showHideTodoModeSettings(animated: true) Notification.Name.todoMenuToggled.post() } @@ -226,9 +236,22 @@ class SettingsViewController: NSViewController { initializeTodoModeSettings() + self.cycleSizeCheckboxes.forEach { + $0.removeFromSuperview() + } + + let cycleSizeCheckboxes = makeCycleSizeCheckboxes() + cycleSizeCheckboxes.forEach { checkbox in + cycleSizesView.addArrangedSubview(checkbox) + } + self.cycleSizeCheckboxes = cycleSizeCheckboxes + + initializeCycleSizesView(animated: false) + Notification.Name.configImported.onPost(using: {_ in self.initializeTodoModeSettings() self.initializeToggles() + self.initializeCycleSizesView(animated: false) }) Notification.Name.menuBarIconHidden.onPost(using: {_ in @@ -249,11 +272,15 @@ class SettingsViewController: NSViewController { TodoManager.initReflowShortcut() toggleTodoShortcutView.setAssociatedUserDefaultsKey(TodoManager.toggleDefaultsKey, withTransformerName: MASDictionaryTransformerName) reflowTodoShortcutView.setAssociatedUserDefaultsKey(TodoManager.reflowDefaultsKey, withTransformerName: MASDictionaryTransformerName) - showHideTodoModeSettings() + showHideTodoModeSettings(animated: false) } - private func showHideTodoModeSettings() { - todoView.isHidden = !Defaults.todo.userEnabled + private func showHideTodoModeSettings(animated: Bool) { + animateChanges(animated: animated) { + let isEnabled = Defaults.todo.userEnabled + todoView.isHidden = !isEnabled + todoViewHeightConstraint.isActive = !isEnabled + } } func initializeToggles() { @@ -282,6 +309,92 @@ class SettingsViewController: NSViewController { } else { stageView.isHidden = true } + + + setToggleStatesForCycleSizeCheckboxes() + } + + private func initializeCycleSizesView(animated: Bool = false) { + let showOptionsView = Defaults.subsequentExecutionMode.value == .resize + + if showOptionsView { + setToggleStatesForCycleSizeCheckboxes() + } + + animateChanges(animated: animated) { + cycleSizesView.isHidden = !showOptionsView + cycleSizesViewHeightConstraint.isActive = !showOptionsView + } + } + + private func animateChanges(animated: Bool, block: () -> Void) { + if animated { + NSAnimationContext.runAnimationGroup({context in + context.duration = 0.3 + context.allowsImplicitAnimation = true + + block() + view.layoutSubtreeIfNeeded() + }, completionHandler: nil) + } else { + block() + } + } + + private func makeCycleSizeCheckboxes() -> [NSButton] { + CycleSize.sortedSizes.map { division in + let button = NSButton(checkboxWithTitle: division.title, target: self, action: #selector(didCheckCycleSizeCheckbox(sender:))) + button.tag = division.rawValue + button.setContentCompressionResistancePriority(.required, for: .vertical) + return button + } + } + + @objc private func didCheckCycleSizeCheckbox(sender: Any?) { + guard let checkbox = sender as? NSButton else { + Logger.log("Expected action to be sent from NSButton. Instead, sender is: \(String(describing: sender))") + return + } + + let rawValue = checkbox.tag + + guard let cycleSize = CycleSize(rawValue: rawValue) else { + Logger.log("Expected tag of cycle size checkbox to match a value of CycleSize. Got: \(String(describing: rawValue))") + return + } + + // If selected cycle sizes has not been changed, write the defaults. + if !Defaults.cycleSizesIsChanged.enabled { + Defaults.selectedCycleSizes.value = CycleSize.defaultSizes + } + + Defaults.cycleSizesIsChanged.enabled = true + + if checkbox.state == .on { + Defaults.selectedCycleSizes.value.insert(cycleSize) + } else { + Defaults.selectedCycleSizes.value.remove(cycleSize) + } + } + + private func setToggleStatesForCycleSizeCheckboxes() { + let useDefaultCycleSizes = !Defaults.cycleSizesIsChanged.enabled + let cycleSizes = useDefaultCycleSizes ? CycleSize.defaultSizes : Defaults.selectedCycleSizes.value + + cycleSizeCheckboxes.forEach { checkbox in + guard let cycleSizeForCheckbox = CycleSize(rawValue: checkbox.tag) else { + return + } + + let isAlwaysEnabled = cycleSizeForCheckbox.isAlwaysEnabled + let isChecked = isAlwaysEnabled || cycleSizes.contains(cycleSizeForCheckbox) + checkbox.state = isChecked ? .on : .off + + // Show that the box cannot be unchecked. + if isAlwaysEnabled { + checkbox.isEnabled = false + } + } } } diff --git a/Rectangle/WindowCalculation/RepeatedExecutionsCalculation.swift b/Rectangle/WindowCalculation/RepeatedExecutionsCalculation.swift index b12c78ad..160f5b52 100644 --- a/Rectangle/WindowCalculation/RepeatedExecutionsCalculation.swift +++ b/Rectangle/WindowCalculation/RepeatedExecutionsCalculation.swift @@ -12,9 +12,7 @@ protocol RepeatedExecutionsCalculation { func calculateFirstRect(_ params: RectCalculationParameters) -> RectResult - func calculateSecondRect(_ params: RectCalculationParameters) -> RectResult - - func calculateThirdRect(_ params: RectCalculationParameters) -> RectResult + func calculateRect(for cycleDivision: CycleSize, params: RectCalculationParameters) -> RectResult } @@ -27,18 +25,16 @@ extension RepeatedExecutionsCalculation { else { return calculateFirstRect(params) } - - let position = count % 3 - switch (position) { - case 1: - return calculateSecondRect(params) - case 2: - return calculateThirdRect(params) - default: - return calculateFirstRect(params) - } + let useDefaultPositions = !Defaults.cycleSizesIsChanged.enabled + let positions = useDefaultPositions ? CycleSize.defaultSizes : Defaults.selectedCycleSizes.value + + let sortedPositions = CycleSize.sortedSizes + .filter { positions.contains($0) } + + let position = count % sortedPositions.count + return calculateRect(for: sortedPositions[position], params: params) } } diff --git a/Rectangle/WindowCalculation/RepeatedExecutionsInThirdsCalculation.swift b/Rectangle/WindowCalculation/RepeatedExecutionsInThirdsCalculation.swift index 9ef75d18..5e6590db 100644 --- a/Rectangle/WindowCalculation/RepeatedExecutionsInThirdsCalculation.swift +++ b/Rectangle/WindowCalculation/RepeatedExecutionsInThirdsCalculation.swift @@ -20,13 +20,8 @@ extension RepeatedExecutionsInThirdsCalculation { return calculateFractionalRect(params, fraction: 1 / 2.0) } - func calculateSecondRect(_ params: RectCalculationParameters) -> RectResult { - let fraction: Float = Defaults.altThirdCycle.userEnabled ? (1 / 3.0) : (2 / 3.0) - return calculateFractionalRect(params, fraction: fraction) - } - - func calculateThirdRect(_ params: RectCalculationParameters) -> RectResult { - let fraction: Float = Defaults.altThirdCycle.userEnabled ? (2 / 3.0) : (1 / 3.0) + func calculateRect(for cycleDivision: CycleSize, params: RectCalculationParameters) -> RectResult { + let fraction = cycleDivision.fraction return calculateFractionalRect(params, fraction: fraction) } diff --git a/Rectangle/mul.lproj/Main.xcstrings b/Rectangle/mul.lproj/Main.xcstrings index 162a10ac..ae6e761c 100644 --- a/Rectangle/mul.lproj/Main.xcstrings +++ b/Rectangle/mul.lproj/Main.xcstrings @@ -19003,186 +19003,156 @@ } }, "gHH-BV-5kP.title" : { - "comment" : "Class = \"NSMenuItem\"; title = \"cycle ½, ⅔, and ⅓ on half actions\"; ObjectID = \"gHH-BV-5kP\";", + "comment" : "Class = \"NSMenuItem\"; title = \"cycle sizes on half actions\"; ObjectID = \"gHH-BV-5kP\";", "extractionState" : "extracted_with_value", "localizations" : { "ar" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "دورة ½ و ⅔ و على نصف الإجراءات" } }, "ca" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "cicle ½, ⅔, i ⅓ a mitges accions" } }, "ca-ES" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "cicle ½, ⅔, i ⅓ a mitges accions" } }, "cs" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "změnit šířku oken na ½, ⅔, a ⅓ u polovičních akcí" } }, "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Wechsle zwischen ½, ⅔, und ⅓ bei Aktionen für halbe Bildschirme" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "cycle ½, ⅔, and ⅓ on half actions" + "value" : "cycle sizes on half actions" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "bucle de ½, ⅔, y ⅓ en acciones de mitad" } }, "es-419" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "bucle de ½, ⅔, y ⅓ en acciones de mitad" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "alterner entre ½, ⅔ et ⅓ lors des actions de moitié d’écran" } }, "id" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "siklus ½, ⅔ dan ⅓ pada setengah tindakan" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "scorri tra ½, ⅔, e ⅓ con le azioni di mezzi" } }, "ja" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "ウインドウの幅を1/2、2/3、1/3に順次変更" } }, "ko" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "절반 동작에 대해 1/2, 2/3, 1/3 동작을 순환" } }, "lt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "perjungti tarp ½, ⅔ ir ⅓ pusėms" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "bytt mellom ½, ⅔, og ⅓ for halv-handlinger" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "cycle ½, ⅔, and ⅓ on half actions" + "value" : "bytt mellom størrelser for halv-handlinger" } }, "nn" : { "stringUnit" : { "state" : "translated", - "value" : "byt mellom ½, ⅔, og ⅓ for halv-handlingar" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "cycle ½, ⅔, and ⅓ on half actions" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "cycle ½, ⅔, and ⅓ on half actions" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "cycle ½, ⅔, and ⅓ on half actions" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "cycle ½, ⅔, and ⅓ on half actions" + "value" : "byt mellom storleikar for halv-handlingar" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "переключаться между ½, ⅔ и ⅓ для половин" } }, "sk" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "zmeniť šírku okien na ½, ⅔, a ⅓ pri polovičných akciách" } }, "sv-SE" : { "stringUnit" : { "state" : "translated", - "value" : "växla mellan ½, ⅔, och ⅓ på halva åtgärder" + "value" : "växla mellan storlek på halva åtgärder" } }, "tr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "yarım seçenekleri yinelenirse ½, ⅔, ve ⅓ oranlarında geçiş yap" } }, "uk" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "перемикати ½, ⅔, and ⅓ при діях з половиною" } }, "vi" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Thay đổi tuần hoàn ½, ⅔, và ⅓ kích thước" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "半屏操作循环½、⅔、⅓" } }, "zh-Hant" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "一半動作循環½、⅔和⅓" } }, "zh-Hant-HK" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "一半動作循環½、⅔和⅓" } } @@ -43131,7 +43101,7 @@ "en-GB" : { "stringUnit" : { "state" : "translated", - "value": "Centre" + "value" : "Centre" } }, "es" : { @@ -47510,7 +47480,7 @@ }, "nb" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Innstillinger…" } }, @@ -47522,7 +47492,7 @@ }, "nn" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Innstillingar…" } }, @@ -47564,7 +47534,7 @@ }, "sv-SE" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Inställningar…" } },