diff --git a/AppIntents/ContactState.swift b/AppIntents/ContactState.swift new file mode 100644 index 000000000..139241c74 --- /dev/null +++ b/AppIntents/ContactState.swift @@ -0,0 +1,26 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +enum ContactState: String, AppEnum { + case on = "ON" + case off = "OFF" + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Contact State") + + static let caseDisplayRepresentations: [Self: DisplayRepresentation] = [ + .on: "On", + .off: "Off" + ] +} diff --git a/AppIntents/Home.swift b/AppIntents/Home.swift new file mode 100644 index 000000000..921bfee66 --- /dev/null +++ b/AppIntents/Home.swift @@ -0,0 +1,240 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +// MARK: - Stable Cross-Device Identifier + +extension HomePreferences { + /// A stable identifier that is the same on every device configured for the same openHAB server. + /// Used as Home.id so that shortcuts synced via iCloud resolve correctly on a second device. + /// + /// Priority: + /// 1. cloudUserId — unique per myopenhab.org account + /// 2. remote URL — unique for self-hosted cloud servers + /// 3. local URL — fallback for local-only setups + /// 4. UUID string — last resort (no cross-device benefit, but avoids an empty identifier) + var stableIdentifier: String { + if let cloudId = remoteConnectionConfig.cloudUserId, !cloudId.isEmpty { + return cloudId + } + if !remoteConnectionConfig.url.isEmpty { + return remoteConnectionConfig.url + } + if !localConnectionConfig.url.isEmpty { + return localConnectionConfig.url + } + return id.uuidString + } +} + +// MARK: - Home AppEntity + +struct Home: AppEntity { + struct HomeQuery: EntityQuery { + @MainActor + func entities(for identifiers: [Home.ID]) async throws -> [Home] { + let storedHomes = Preferences.shared.storedHomes + return identifiers.compactMap { identifier in + // Current format: match by stable cross-device identifier + if let match = storedHomes.values.first(where: { $0.stableIdentifier == identifier }) { + return Home(homePrefs: match) + } + // Legacy format: identifier is a device-local UUID string (shortcuts before this fix) + if let uuid = UUID(uuidString: identifier), let match = storedHomes[uuid] { + return Home(homePrefs: match) + } + return nil + } + } + + @MainActor + func suggestedEntities() async throws -> [Home] { + Preferences.shared.storedHomes.values + .sorted { + let nameOrder = $0.homeName.localizedCaseInsensitiveCompare($1.homeName) + if nameOrder != .orderedSame { + return nameOrder == .orderedAscending + } + return $0.id.uuidString < $1.id.uuidString + } + .map { Home(homePrefs: $0) } + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Home") + + static let defaultQuery = HomeQuery() + + var id: String + var displayString: String + var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(displayString)") + } + + init(id: String, displayString: String) { + self.id = id + self.displayString = displayString + } + + @MainActor + init(homePrefs: HomePreferences) { + self.init(id: homePrefs.stableIdentifier, displayString: homePrefs.homeName) + } +} + +// MARK: - Errors + +enum HomeResolutionError: Error, CustomLocalizedStringResourceConvertible { + case unknownHome + case ambiguousHomeSelection(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case .unknownHome: + "Unknown home" + case let .ambiguousHomeSelection(itemName): + "Select a home for '\(itemName)'" + } + } +} + +// MARK: - HomeResolver + +enum HomeResolver { + /// Validates that `selectedHome` matches `itemHomeId` and returns the resolved local UUID. + /// + /// Resolution order: + /// 1. Match `selectedHome.id` against entries in `stableIdentifierToLocalUUID`. + /// 2. Fall back to treating `selectedHome.id` as a legacy device-local UUID string. + /// + /// The default empty array skips stable-identifier lookup and falls straight through to the + /// UUID fallback — this keeps unit tests working without any Preferences setup. + static func resolvedHomeId( + selectedHome: Home?, + itemHomeId: UUID, + itemLabel: String, + stableIdentifierToLocalUUID: [(String, UUID)] = [], + mismatchError: (String, String) -> E + ) throws -> UUID { + guard let selectedHome else { + return itemHomeId + } + + let homeId: UUID + if let match = stableIdentifierToLocalUUID.first(where: { $0.0 == selectedHome.id }) { + homeId = match.1 + } else if let uuid = UUID(uuidString: selectedHome.id) { + homeId = uuid + } else { + throw HomeResolutionError.unknownHome + } + + guard homeId == itemHomeId else { + throw mismatchError(itemLabel, selectedHome.displayString) + } + + return homeId + } + + /// Production overload — builds the stable-identifier map on the main actor before delegating + /// to the testable sync overload. Intent `perform()` methods call this with `try await`. + @MainActor + static func resolvedHomeId( + selectedHome: Home?, + itemHomeId: UUID, + itemLabel: String, + mismatchError: (String, String) -> E + ) async throws -> UUID { + let map = Preferences.shared.storedHomes.values.map { ($0.stableIdentifier, $0.id) } + return try resolvedHomeId( + selectedHome: selectedHome, + itemHomeId: itemHomeId, + itemLabel: itemLabel, + stableIdentifierToLocalUUID: map, + mismatchError: mismatchError + ) + } + + /// Production overload for item-by-name resolution (iOS 16 compat intents). + /// Builds a stable-identifier-aware `findHomeId` closure and delegates to the testable overload. + static func resolveHomeId( + selectedHome: Home?, + itemName: String, + allowedTypes: [OpenHABItem.ItemType]? = nil + ) async throws -> UUID { + try await resolveHomeId( + selectedHome: selectedHome, + itemName: itemName, + findHomeId: { identifier in + // All HomePreferences property access must happen on the main actor. + await MainActor.run { + let storedHomes = Preferences.shared.storedHomes + if let match = storedHomes.values.first(where: { $0.stableIdentifier == identifier }) { + return match.id + } + if let uuid = UUID(uuidString: identifier), storedHomes[uuid] != nil { + return uuid + } + return nil + } + }, + listStoredHomes: { await Preferences.shared.listStoredHomes() }, + exactMatchedHomes: { + let searchResults = await OpenHABItemCache.instance.searchCachedOrPersistedItems( + searchTerm: itemName, + types: allowedTypes + ) + let normalizedItemName = itemName.trimmingCharacters(in: .whitespacesAndNewlines) + + return Set(searchResults.flatMap { homeId, items in + items.compactMap { item in + item.name.localizedCaseInsensitiveCompare(normalizedItemName) == .orderedSame ? homeId : nil + } + }) + } + ) + } + + /// Testable overload. All parameters are injected via closures so unit tests can provide + /// mock implementations. + /// + /// `findHomeId` defaults to UUID-string parsing so existing tests that only exercise the + /// `selectedHome: nil` path compile and run without modification. + static func resolveHomeId( + selectedHome: Home?, + itemName: String, + findHomeId: @escaping (String) async -> UUID? = { UUID(uuidString: $0) }, + listStoredHomes: @escaping () async -> [UUID], + exactMatchedHomes: @escaping () async -> Set + ) async throws -> UUID { + if let selectedHome { + guard let homeId = await findHomeId(selectedHome.id) else { + throw HomeResolutionError.unknownHome + } + return homeId + } + + let storedHomes = await listStoredHomes() + if storedHomes.count == 1, let onlyHomeId = storedHomes.first { + return onlyHomeId + } + + let matchedHomes = await exactMatchedHomes() + if matchedHomes.count == 1, let matchedHomeId = matchedHomes.first { + return matchedHomeId + } + + throw HomeResolutionError.ambiguousHomeSelection(itemName) + } +} diff --git a/AppIntents/Intents/ContactStateIntent.swift b/AppIntents/Intents/ContactStateIntent.swift new file mode 100644 index 000000000..de7226c9d --- /dev/null +++ b/AppIntents/Intents/ContactStateIntent.swift @@ -0,0 +1,77 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum ContactStateError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct ContactStateIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.contact] } + + static var parameterSummary: some ParameterSummary { + Summary("Set the state of \(\.$itemEntity) to \(\.$state)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Contact State Value" + static let description = IntentDescription("Set the state of a contact open or closed") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: ContactItemEntity + + @Parameter(title: "State") + var state: ContactState + + func perform() async throws -> some IntentResult & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: ContactStateError.itemNotInHome + ) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: state.rawValue + ) + } catch { + throw ContactStateError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "The state of \(itemEntity.label) was set to \(state.rawValue)") + } +} diff --git a/AppIntents/Intents/GetItemStateIntent.swift b/AppIntents/Intents/GetItemStateIntent.swift new file mode 100644 index 000000000..9415c3d76 --- /dev/null +++ b/AppIntents/Intents/GetItemStateIntent.swift @@ -0,0 +1,65 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum ItemStateError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct GetItemStateIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var parameterSummary: some ParameterSummary { + Summary("Get \(\.$itemEntity) State") { + \.$home + } + } + + static let title: LocalizedStringResource = "Get Item State" + static let description = IntentDescription("Retrieve the current state of an item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: GenericItemEntity + + func perform() async throws -> some IntentResult & ReturnsValue & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: ItemStateError.itemNotInHome + ) + + let freshItem = await OpenHABItemCache.instance.getItemUncached(name: itemEntity.itemName, home: homeId) + let state = freshItem?.state ?? itemEntity.item.state ?? "Unknown state" + + return .result( + value: state, + dialog: "The state of \(itemEntity.label) is \(state)" + ) + } +} diff --git a/AppIntents/Intents/SetColorValueIntent.swift b/AppIntents/Intents/SetColorValueIntent.swift new file mode 100644 index 000000000..c03eb3914 --- /dev/null +++ b/AppIntents/Intents/SetColorValueIntent.swift @@ -0,0 +1,90 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum ColorValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case invalidValue(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .invalidValue(value, itemName): + "Invalid value: \(value) for \(itemName) must be HSB (0-360,0-100,0-100)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetColorValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.color] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$value) (HSB)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Color Control Value" + static let description = IntentDescription("Set the color of a color control item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: ColorItemEntity + + @Parameter(title: "Value", default: "240,100,100") + var value: String + + func perform() async throws -> some IntentResult & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: ColorValueError.itemNotInHome + ) + + var colorValue = value + let hsb = colorValue.split(separator: ",") + guard hsb.count == 3, + let hue = Int(hsb[0]), (0 ... 360).contains(hue), + let sat = Int(hsb[1]), (0 ... 100).contains(sat), + let val = Int(hsb[2]), (0 ... 100).contains(val) else { + throw ColorValueError.invalidValue(colorValue, itemEntity.label) + } + + colorValue = "\(hue),\(sat),\(val)" + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: colorValue + ) + } catch { + throw ColorValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the color value of \(colorValue) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetDateTimeValueIntent.swift b/AppIntents/Intents/SetDateTimeValueIntent.swift new file mode 100644 index 000000000..b33e1241d --- /dev/null +++ b/AppIntents/Intents/SetDateTimeValueIntent.swift @@ -0,0 +1,79 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum DateTimeValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetDateTimeValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.dateTime] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$value)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set DateTime Control Value" + static let description = IntentDescription("Set the date and time of a DateTime control item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: DateTimeItemEntity + + @Parameter(title: "Date and Time") + var value: Date + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: DateTimeValueError.itemNotInHome + ) + + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + let command = formatter.string(from: value) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: command + ) + } catch { + throw DateTimeValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the date \(command) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetDimmerRollerValueIntent.swift b/AppIntents/Intents/SetDimmerRollerValueIntent.swift new file mode 100644 index 000000000..7de819081 --- /dev/null +++ b/AppIntents/Intents/SetDimmerRollerValueIntent.swift @@ -0,0 +1,83 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum DimmerRollerValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case invalidValue(Int, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .invalidValue(value, itemName): + "Invalid value \(value) for \(itemName) (0-100)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetDimmerRollerValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.dimmer, .rollershutter] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$value)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Dimmer or Roller Shutter Value" + static let description = IntentDescription("Set the integer value of a dimmer or roller shutter") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: DimmerItemEntity + + @Parameter(title: "Value") + var value: Int + + func perform() async throws -> some IntentResult & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: DimmerRollerValueError.itemNotInHome + ) + + guard (0 ... 100).contains(value) else { + throw DimmerRollerValueError.invalidValue(value, itemEntity.label) + } + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: "\(value)" + ) + } catch { + throw DimmerRollerValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the value of \(value) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetLocationValueIntent.swift b/AppIntents/Intents/SetLocationValueIntent.swift new file mode 100644 index 000000000..aeb8b645c --- /dev/null +++ b/AppIntents/Intents/SetLocationValueIntent.swift @@ -0,0 +1,94 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum LocationValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case invalidLatitude + case invalidLongitude + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case .invalidLatitude: + "Latitude must be between -90 and 90" + case .invalidLongitude: + "Longitude must be between -180 and 180" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetLocationValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.location] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$latitude), \(\.$longitude)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Location Control Value" + static let description = IntentDescription("Set the latitude and longitude of a location control item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: LocationItemEntity + + @Parameter(title: "Latitude") + var latitude: Double + + @Parameter(title: "Longitude") + var longitude: Double + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: LocationValueError.itemNotInHome + ) + + guard (-90 ... 90).contains(latitude) else { + throw LocationValueError.invalidLatitude + } + + guard (-180 ... 180).contains(longitude) else { + throw LocationValueError.invalidLongitude + } + + let command = "\(latitude),\(longitude)" + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: command + ) + } catch { + throw LocationValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent location \(latitude), \(longitude) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetNumberValueIntent.swift b/AppIntents/Intents/SetNumberValueIntent.swift new file mode 100644 index 000000000..11065e022 --- /dev/null +++ b/AppIntents/Intents/SetNumberValueIntent.swift @@ -0,0 +1,76 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum NumberValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetNumberValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.number, .numberWithDimension] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$value)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Number Control Value" + static let description = IntentDescription("Set the decimal value of a number control item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: NumberItemEntity + + @Parameter(title: "Value") + var value: Double + + func perform() async throws -> some IntentResult & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: NumberValueError.itemNotInHome + ) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: String(value) + ) + } catch { + throw NumberValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the number \(value) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetPlayerValueIntent.swift b/AppIntents/Intents/SetPlayerValueIntent.swift new file mode 100644 index 000000000..d10c6e724 --- /dev/null +++ b/AppIntents/Intents/SetPlayerValueIntent.swift @@ -0,0 +1,76 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum PlayerValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetPlayerValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.player] } + + static var parameterSummary: some ParameterSummary { + Summary("Send \(\.$action) to \(\.$itemEntity)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Player Control Value" + static let description = IntentDescription("Send a player command such as play, pause, next, or previous") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: PlayerItemEntity + + @Parameter(title: "Action") + var action: PlayerAction + + func perform() async throws -> some IntentResult { + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: PlayerValueError.itemNotInHome + ) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: action.rawValue + ) + } catch { + throw PlayerValueError.commandFailed(error.localizedDescription) + } + + return .result() + } +} diff --git a/AppIntents/Intents/SetStringValueIntent.swift b/AppIntents/Intents/SetStringValueIntent.swift new file mode 100644 index 000000000..8705229e4 --- /dev/null +++ b/AppIntents/Intents/SetStringValueIntent.swift @@ -0,0 +1,76 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum StringValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetStringValueIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.stringItem] } + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$itemEntity) to \(\.$value)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set String Control Value" + static let description = IntentDescription("Set the string of a string control item") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: StringItemEntity + + @Parameter(title: "Value") + var value: String + + func perform() async throws -> some IntentResult & ProvidesDialog { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: StringValueError.itemNotInHome + ) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: value + ) + } catch { + throw StringValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the string \(value) to \(itemEntity.label)") + } +} diff --git a/AppIntents/Intents/SetSwitchItemIntent.swift b/AppIntents/Intents/SetSwitchItemIntent.swift new file mode 100644 index 000000000..e5462b3df --- /dev/null +++ b/AppIntents/Intents/SetSwitchItemIntent.swift @@ -0,0 +1,77 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +enum ControlItemError: Error, CustomLocalizedStringResourceConvertible { + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SetSwitchItemIntent: AppIntent { + static var openAppWhenRun: Bool { false } + + static var allowedItemTypes: [OpenHABItem.ItemType] { [.switchItem] } + + static var parameterSummary: some ParameterSummary { + Summary("Send \(\.$action) to \(\.$itemEntity)") { + \.$home + } + } + + static let title: LocalizedStringResource = "Set Switch State" + static let description = IntentDescription("Set the state of a switch on or off, or toggle its state") + + @Parameter(title: "Home") + var home: Home? + + @Parameter( + title: "Item", + requestValueDialog: IntentDialog("Search for an item") + ) + var itemEntity: SwitchItemEntity + + @Parameter(title: "Action") + var action: SwitchAction + + func perform() async throws -> some IntentResult { + // Validate that the item belongs to the selected home + let homeId = try await HomeResolver.resolvedHomeId( + selectedHome: home, + itemHomeId: itemEntity.homeId, + itemLabel: itemEntity.label, + mismatchError: ControlItemError.itemNotInHome + ) + + do { + try await OpenHABItemCache.instance.sendCommand( + to: itemEntity.item, + home: homeId, + command: action.rawValue + ) + } catch { + throw ControlItemError.commandFailed(error.localizedDescription) + } + + return .result() + } +} diff --git a/AppIntents/ItemEntity.swift b/AppIntents/ItemEntity.swift new file mode 100644 index 000000000..ad76d1003 --- /dev/null +++ b/AppIntents/ItemEntity.swift @@ -0,0 +1,64 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +// MARK: - Shared Protocol for All Item Entities + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +protocol ItemEntity: AppEntity where ID == ItemIdentifier { + var id: ItemIdentifier { get set } + var item: OpenHABItem { get set } + var homeName: String? { get set } + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String?) + init(_ openHABItem: OpenHABItem, homeId: UUID, homeName: String?) +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +extension ItemEntity { + var homeId: UUID { id.homeId } + var itemName: String { id.itemName } + + // Convenient access to common item properties + var label: String { item.label } + var category: String { item.category } + var type: OpenHABItem.ItemType? { item.type } + var state: String? { item.state } + var link: String { item.link } + + var displayRepresentation: DisplayRepresentation { + if let homeName { + DisplayRepresentation( + title: nonLocalizedDisplayString(label, key: "__app_intents_item_label__"), + subtitle: nonLocalizedDisplayString("\(item.name) • \(homeName)", key: "__app_intents_item_subtitle__") + ) + } else { + DisplayRepresentation( + title: nonLocalizedDisplayString(label, key: "__app_intents_item_label__"), + subtitle: nonLocalizedDisplayString(item.name, key: "__app_intents_item_name__") + ) + } + } + + init(_ openHABItem: OpenHABItem, homeId: UUID, homeName: String? = nil) { + self.init( + id: ItemIdentifier(homeId: homeId, itemName: openHABItem.name), + item: openHABItem, + homeName: homeName + ) + } + + func nonLocalizedDisplayString(_ value: String, key: StaticString) -> LocalizedStringResource { + LocalizedStringResource(key, defaultValue: "\(value)") + } +} diff --git a/AppIntents/ItemEntityQuery.swift b/AppIntents/ItemEntityQuery.swift new file mode 100644 index 000000000..0cfc0dcee --- /dev/null +++ b/AppIntents/ItemEntityQuery.swift @@ -0,0 +1,145 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +// MARK: - Shared Query Protocol + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +protocol ItemEntityQuery: EntityStringQuery { + associatedtype EntityType: ItemEntity + var allowedTypes: [OpenHABItem.ItemType] { get set } + var selectedHome: Home? { get } +} + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +extension ItemEntityQuery { + @MainActor + func getHomeName(for homeId: UUID) -> String? { + Preferences.shared.storedHomes[homeId]?.homeName + } + + @MainActor + func resolvedSelectedHomeId() -> UUID? { + guard let home = selectedHome else { return nil } + let storedHomes = Preferences.shared.storedHomes + if let match = storedHomes.values.first(where: { $0.stableIdentifier == home.id }) { + return match.id + } + return UUID(uuidString: home.id) + } + + func entityResults(from itemsByHome: [UUID: [OpenHABItem]]) async -> [EntityType] { + var result: [EntityType] = [] + + for (homeId, items) in itemsByHome { + let homeName = await getHomeName(for: homeId) + result.append(contentsOf: items.map { EntityType($0, homeId: homeId, homeName: homeName) }) + } + + return result.sorted { + if $0.item.name.localizedCaseInsensitiveCompare($1.item.name) != .orderedSame { + return $0.item.name.localizedCaseInsensitiveCompare($1.item.name) == .orderedAscending + } + + let leftHomeName = $0.homeName ?? "" + let rightHomeName = $1.homeName ?? "" + return leftHomeName.localizedCaseInsensitiveCompare(rightHomeName) == .orderedAscending + } + } + + func uniqueExactMatch(from itemsByHome: [UUID: [OpenHABItem]], searchTerm: String) async -> EntityType? { + let normalizedSearchTerm = searchTerm.trimmingCharacters(in: .whitespacesAndNewlines) + guard !normalizedSearchTerm.isEmpty else { + return nil + } + + let matches = itemsByHome.flatMap { homeId, items in + items.compactMap { item in + isExactMatch(item, searchTerm: normalizedSearchTerm) ? (homeId, item) : nil + } + } + + guard matches.count == 1, let match = matches.first else { + return nil + } + + let homeName = await getHomeName(for: match.0) + return EntityType(match.1, homeId: match.0, homeName: homeName) + } + + func entities(for identifiers: [ItemIdentifier]) async throws -> [EntityType] { + var result: [EntityType] = [] + + for identifier in identifiers { + if let item = await OpenHABItemCache.instance.getCachedOrPersistedItem( + name: identifier.itemName, + home: identifier.homeId + ) { + let homeName = await getHomeName(for: identifier.homeId) + result.append(EntityType(item, homeId: identifier.homeId, homeName: homeName)) + } + } + + return result + } + + func suggestedEntities() async throws -> [EntityType] { + // If the user selected a Home in the intent UI, scope results to that home. + if let selectedHomeId = await resolvedSelectedHomeId() { + await OpenHABItemCache.instance.reloadCacheIfNeeded(homes: [selectedHomeId]) + let itemsByHome = await OpenHABItemCache.instance.getCachedOrPersistedItems( + types: allowedTypes.isEmpty ? nil : allowedTypes, + homes: [selectedHomeId] + ) + return await entityResults(from: itemsByHome) + } + + // Fallback (e.g. Siri request without an explicit Home selection): return items across all homes. + let storedHomes = await Preferences.shared.listStoredHomes() + await OpenHABItemCache.instance.reloadCacheIfNeeded(homes: storedHomes) + let itemsByHome = await OpenHABItemCache.instance.getCachedOrPersistedItems( + types: allowedTypes.isEmpty ? nil : allowedTypes, + homes: storedHomes + ) + return await entityResults(from: itemsByHome) + } + + func entities(matching string: String) async throws -> [EntityType] { + let selectedHomeId = await resolvedSelectedHomeId() + let searchResults = await OpenHABItemCache.instance.searchCachedOrPersistedItems( + searchTerm: string, + types: allowedTypes.isEmpty ? nil : allowedTypes, + homes: selectedHomeId.map { [$0] } + ) + + // If the user selected a Home in the intent UI, scope results to that home. + if selectedHomeId != nil { + return await entityResults(from: searchResults) + } + + // If the typed item name resolves to exactly one item across all homes, + // prefer that single result to avoid unnecessary cross-home ambiguity. + if let uniqueExactMatch = await uniqueExactMatch(from: searchResults, searchTerm: string) { + return [uniqueExactMatch] + } + + // Fallback (e.g. Siri request without an explicit Home selection): return matches across all homes. + return await entityResults(from: searchResults) + } + + func isExactMatch(_ item: OpenHABItem, searchTerm: String) -> Bool { + let hasExactNameMatch = item.name.localizedCaseInsensitiveCompare(searchTerm) == .orderedSame + let hasExactLabelMatch = item.label.localizedCaseInsensitiveCompare(searchTerm) == .orderedSame + return hasExactNameMatch || hasExactLabelMatch + } +} diff --git a/AppIntents/ItemIdentifier.swift b/AppIntents/ItemIdentifier.swift new file mode 100644 index 000000000..dc3d48105 --- /dev/null +++ b/AppIntents/ItemIdentifier.swift @@ -0,0 +1,33 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +struct ItemIdentifier: Hashable, Codable { + let homeId: UUID + let itemName: String +} + +extension ItemIdentifier: EntityIdentifierConvertible { + var entityIdentifierString: String { + "\(homeId.uuidString):\(itemName)" + } + + static func entityIdentifier(for entityIdentifierString: String) -> ItemIdentifier? { + let components = entityIdentifierString.split(separator: ":", maxSplits: 1) + guard components.count == 2, + let homeId = UUID(uuidString: String(components[0])) else { + return nil + } + return ItemIdentifier(homeId: homeId, itemName: String(components[1])) + } +} diff --git a/AppIntents/PlayerAction.swift b/AppIntents/PlayerAction.swift new file mode 100644 index 000000000..fd551b8ca --- /dev/null +++ b/AppIntents/PlayerAction.swift @@ -0,0 +1,34 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +enum PlayerAction: String, AppEnum { + case play = "PLAY" + case pause = "PAUSE" + case next = "NEXT" + case previous = "PREVIOUS" + case rewind = "REWIND" + case fastforward = "FASTFORWARD" + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Player Action") + + static let caseDisplayRepresentations: [Self: DisplayRepresentation] = [ + .play: "Play", + .pause: "Pause", + .next: "Next", + .previous: "Previous", + .rewind: "Rewind", + .fastforward: "Fast Forward" + ] +} diff --git a/AppIntents/SwitchAction.swift b/AppIntents/SwitchAction.swift new file mode 100644 index 000000000..92e41de85 --- /dev/null +++ b/AppIntents/SwitchAction.swift @@ -0,0 +1,28 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +enum SwitchAction: String, AppEnum { + case on = "ON" + case off = "OFF" + case toggle = "TOGGLE" + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Switch Action") + + static let caseDisplayRepresentations: [Self: DisplayRepresentation] = [ + .on: "On", + .off: "Off", + .toggle: "Toggle" + ] +} diff --git a/AppIntents/SwitchItemEntity.swift b/AppIntents/SwitchItemEntity.swift new file mode 100644 index 000000000..96fe50f34 --- /dev/null +++ b/AppIntents/SwitchItemEntity.swift @@ -0,0 +1,343 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import OpenHABCore + +// MARK: - DimmerItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct DimmerItemEntity: ItemEntity { + struct DimmerItemQuery: ItemEntityQuery { + typealias EntityType = DimmerItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.dimmer, .rollershutter] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Dimmer/Roller Item") + static let defaultQuery = DimmerItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - ColorItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct ColorItemEntity: ItemEntity { + struct ColorItemQuery: ItemEntityQuery { + typealias EntityType = ColorItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.color] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Color Item") + static let defaultQuery = ColorItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - NumberItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct NumberItemEntity: ItemEntity { + struct NumberItemQuery: ItemEntityQuery { + typealias EntityType = NumberItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.number, .numberWithDimension] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Number Item") + static let defaultQuery = NumberItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - StringItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct StringItemEntity: ItemEntity { + struct StringItemQuery: ItemEntityQuery { + typealias EntityType = StringItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.stringItem] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "String Item") + static let defaultQuery = StringItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - ContactItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct ContactItemEntity: ItemEntity { + struct ContactItemQuery: ItemEntityQuery { + typealias EntityType = ContactItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.contact] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Contact Item") + static let defaultQuery = ContactItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - GenericItemEntity (for ItemStateIntent - all types) + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct GenericItemEntity: ItemEntity { + struct GenericItemQuery: ItemEntityQuery { + typealias EntityType = GenericItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [] // Empty means all types + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Item") + static let defaultQuery = GenericItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - PlayerItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct PlayerItemEntity: ItemEntity { + struct PlayerItemQuery: ItemEntityQuery { + typealias EntityType = PlayerItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.player] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Player Item") + static let defaultQuery = PlayerItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - DateTimeItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct DateTimeItemEntity: ItemEntity { + struct DateTimeItemQuery: ItemEntityQuery { + typealias EntityType = DateTimeItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.dateTime] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "DateTime Item") + static let defaultQuery = DateTimeItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - LocationItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct LocationItemEntity: ItemEntity { + struct LocationItemQuery: ItemEntityQuery { + typealias EntityType = LocationItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.location] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Location Item") + static let defaultQuery = LocationItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} + +// MARK: - SwitchItemEntity + +@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) +struct SwitchItemEntity: ItemEntity { + struct SwitchItemQuery: ItemEntityQuery { + typealias EntityType = SwitchItemEntity + + @IntentParameterDependency(\.$home) + var intent + + var allowedTypes: [OpenHABItem.ItemType] = [.switchItem] + var selectedHome: Home? { + guard let intent else { + return nil + } + return intent.home + } + } + + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Switch Item") + static let defaultQuery = SwitchItemQuery() + + var id: ItemIdentifier + var item: OpenHABItem + var homeName: String? + + init(id: ItemIdentifier, item: OpenHABItem, homeName: String? = nil) { + self.id = id + self.item = item + self.homeName = homeName + } +} diff --git a/AppIntents/iOS16/ActionMapper.swift b/AppIntents/iOS16/ActionMapper.swift new file mode 100644 index 000000000..719c423cc --- /dev/null +++ b/AppIntents/iOS16/ActionMapper.swift @@ -0,0 +1,48 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import Foundation + +enum ActionMapper { + /// Provides the standard on/off action options for dynamic option providers + static var onOffOptions: [String] { + [ + String(localized: "on").capitalized, + String(localized: "off").capitalized + ] + } + + /// Provides on/off/toggle action options for dynamic option providers + static var onOffToggleOptions: [String] { + [ + String(localized: "on").capitalized, + String(localized: "off").capitalized, + String(localized: "toggle").capitalized + ] + } + + /// Maps a localized action label (e.g., "On", "Off", "Toggle") to an openHAB command (e.g., "ON", "OFF", "TOGGLE") + /// - Parameter localizedAction: The localized action string from user input + /// - Returns: The corresponding openHAB command, or nil if the action is not recognized + static func command(from localizedAction: String) -> String? { + let onLabel = String(localized: "on").capitalized + let offLabel = String(localized: "off").capitalized + let toggleLabel = String(localized: "toggle").capitalized + + let actionMap: [String: String] = [ + onLabel: "ON", + offLabel: "OFF", + toggleLabel: "TOGGLE" + ] + + return actionMap[localizedAction] + } +} diff --git a/AppIntents/iOS16/GetItemState.swift b/AppIntents/iOS16/GetItemState.swift new file mode 100644 index 000000000..4c82ac038 --- /dev/null +++ b/AppIntents/iOS16/GetItemState.swift @@ -0,0 +1,88 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum GetItemStateError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use GetItemStateIntent for iOS 17+") +struct GetItemState: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct StringOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + return allItems.flatMap { $0.value.map(\.name) } + } + } + + static let intentClassName = "OpenHABGetItemStateIntent" + + static let title: LocalizedStringResource = "Get Item State" + static let description = IntentDescription("Retrieve the current state of an item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: StringOptionsProvider()) + var item: String + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Get \(\.$item) State") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$home)) { item, _ in + DisplayRepresentation( + title: "Get \(item) State", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ReturnsValue { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item + ) + + guard let openHABItem = await OpenHABItemCache.instance.getItemUncached(name: item, home: homeId) else { + throw GetItemStateError.itemNotFound(item) + } + + let state = openHABItem.state ?? "Unknown state" + + return .result( + value: state, + dialog: .responseSuccess(item: item, state: state) + ) + } +} + +private extension IntentDialog { + static func responseSuccess(item: String, state: String) -> Self { + "The state of \(item) is \(state)" + } +} diff --git a/AppIntents/iOS16/SetColorValue.swift b/AppIntents/iOS16/SetColorValue.swift new file mode 100644 index 000000000..09c4d261b --- /dev/null +++ b/AppIntents/iOS16/SetColorValue.swift @@ -0,0 +1,143 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetColorValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidValue(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .invalidValue(value, itemName): + "Invalid value: \(value) for \(itemName) must be HSB (0-360,0-100,0-100)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetColorValueIntent for iOS 17+") +struct SetColorValue: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct StringOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .color } + return items.map(\.name) + } + } + + static let intentClassName = "OpenHABSetColorValueIntent" + + static let title: LocalizedStringResource = "Set Color Control Value" + static let description = IntentDescription("Set the color of a color control item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: StringOptionsProvider()) + var item: String + + @Parameter(title: "Value", default: "240,100,100") + var value: String + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$value) (HSB)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$value, \.$home)) { item, value, _ in + DisplayRepresentation( + title: "Set \(item) to \(value) (HSB)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + var colorValue = value + + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.color] + ) + + let hsb = colorValue.split(separator: ",") + guard hsb.count == 3, + let hue = Int(hsb[0]), (0 ... 360).contains(hue), + let sat = Int(hsb[1]), (0 ... 100).contains(sat), + let val = Int(hsb[2]), (0 ... 100).contains(val) else { + throw SetColorValueError.invalidValue(colorValue, item) + } + + colorValue = "\(hue),\(sat),\(val)" + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetColorValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: colorValue) + } catch { + throw SetColorValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(value: colorValue, item: item)) + } +} + +private extension IntentDialog { + static var itemParameterConfiguration: Self { + "Color Item Name" + } + + static var homeParameterConfiguration: Self { + "Home name" + } + + static var homeParameterDisambiguationSelection: Self { + "For which home do you want to get the value?" + } + + static func homeParameterDisambiguationIntro(count: Int, item: String) -> Self { + "There are \(count) configured homes with an item named '\(item)'." + } + + static func homeParameterConfirmation(home: Home) -> Self { + "Just to confirm, you wanted '\(home)'?" + } + + static func responseSuccess(value: String, item: String) -> Self { + "Sent the color value of \(value) to \(item)" + } + + static func responseFailureInvalidItem(item: String) -> Self { + "Sorry can't find \(item)" + } + + static func responseFailureInvalidValue(value: String, item: String) -> Self { + "Invalid value: \(value) for \(item) must be HSB (0-360,0-100,0-100)" + } +} diff --git a/AppIntents/iOS16/SetContactStateValue.swift b/AppIntents/iOS16/SetContactStateValue.swift new file mode 100644 index 000000000..42791b78e --- /dev/null +++ b/AppIntents/iOS16/SetContactStateValue.swift @@ -0,0 +1,145 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetContactStateValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidState(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .invalidState(state, itemName): + "State invalid: \(state) for \(itemName)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use ContactStateIntent for iOS 17+") +struct SetContactStateValue: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct ItemOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .contact } + return items.map(\.name) + } + } + + struct StateOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + ActionMapper.onOffOptions + } + } + + static let intentClassName = "OpenHABSetContactStateValueIntent" + + static let title: LocalizedStringResource = "Set Contact State Value" + static let description = IntentDescription("Set the state of a contact open or closed") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: ItemOptionsProvider()) + var item: String + + @Parameter(title: "State", optionsProvider: StateOptionsProvider()) + var state: String + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set the state of \(\.$item) to \(\.$state)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$state, \.$home)) { item, state, _ in + DisplayRepresentation( + title: "Set the state of \(item) to \(state)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.contact] + ) + + guard let realState = ActionMapper.command(from: state) else { + throw SetContactStateValueError.invalidState(state, item) + } + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetContactStateValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: realState) + } catch { + throw SetContactStateValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(item: item, state: state)) + } +} + +private extension IntentDialog { + static var itemParameterConfiguration: Self { + "Switch name" + } + + static var stateParameterConfiguration: Self { + "Action" + } + + static var homeParameterConfiguration: Self { + "Home name" + } + + static var homeParameterDisambiguationSelection: Self { + "For which home do you want to get the value?" + } + + static func homeParameterDisambiguationIntro(count: Int, item: String) -> Self { + "There are \(count) configured homes with an item named '\(item)'." + } + + static func homeParameterConfirmation(home: Home) -> Self { + "Just to confirm, you wanted '\(home)'?" + } + + static func responseSuccess(item: String, state: String) -> Self { + "The state of \(item) was set to \(state)" + } + + static func responseFailureInvalidItem(item: String) -> Self { + "Sorry can't find \(item)" + } + + static func responseFailureInvalidAction(state: String, item: String) -> Self { + "State invalid: \(state) for \(item)" + } +} diff --git a/AppIntents/iOS16/SetDateTimeValue.swift b/AppIntents/iOS16/SetDateTimeValue.swift new file mode 100644 index 000000000..93bac6ec0 --- /dev/null +++ b/AppIntents/iOS16/SetDateTimeValue.swift @@ -0,0 +1,96 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetDateTimeValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetDateTimeValueIntent for iOS 17+") +struct SetDateTimeValue: AppIntent, PredictableIntent { + struct DateTimeOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .dateTime } + return items.map(\.name) + } + } + + static let title: LocalizedStringResource = "Set DateTime Control Value" + static let description = IntentDescription("Set the date and time of a DateTime control item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: DateTimeOptionsProvider()) + var item: String + + @Parameter(title: "Date and Time") + var value: Date + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$value)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$value, \.$home)) { item, value, _ in + DisplayRepresentation( + title: "Set \(item) to \(value)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.dateTime] + ) + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetDateTimeValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime] + let command = formatter.string(from: value) + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: command) + } catch { + throw SetDateTimeValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the date \(command) to \(item)") + } +} diff --git a/AppIntents/iOS16/SetDimmerRollerValue.swift b/AppIntents/iOS16/SetDimmerRollerValue.swift new file mode 100644 index 000000000..179df0ab8 --- /dev/null +++ b/AppIntents/iOS16/SetDimmerRollerValue.swift @@ -0,0 +1,139 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetDimmerRollerValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidValue(Int, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .invalidValue(value, itemName): + "Invalid value \(value) for \(itemName) (0-100)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetDimmerRollerValueIntent for iOS 17+") +struct SetDimmerRollerValue: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct StringOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .dimmer || $0.type == .rollershutter } + return items.map(\.name) + } + } + + static let intentClassName = "OpenHABSetDimmerRollerValueIntent" + + static let title: LocalizedStringResource = "Set Dimmer or Roller Shutter Value" + static let description = IntentDescription("Set the integer value of a dimmer or roller shutter") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: StringOptionsProvider()) + var item: String + + @Parameter(title: "Value") + var value: Int + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$value)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$value, \.$home)) { item, value, _ in + DisplayRepresentation( + title: "Set \(item) to \(value)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.dimmer, .rollershutter] + ) + + guard (0 ... 100).contains(value) else { + throw SetDimmerRollerValueError.invalidValue(value, item) + } + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetDimmerRollerValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: "\(value)") + } catch { + throw SetDimmerRollerValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(value: value, item: item)) + } +} + +private extension IntentDialog { + static var itemParameterConfiguration: Self { + "Dimmer/Roller Name" + } + + static var homeParameterConfiguration: Self { + "Home name" + } + + static var homeParameterDisambiguationSelection: Self { + "For which home do you want to get the value?" + } + + static func homeParameterDisambiguationIntro(count: Int, item: String) -> Self { + "There are \(count) configured homes with an item named '\(item)'." + } + + static func homeParameterConfirmation(home: Home) -> Self { + "Just to confirm, you wanted '\(home)'?" + } + + static func responseSuccess(value: Int, item: String) -> Self { + "Sent the value of \(value) to \(item)" + } + + static func responseFailureInvalidItem(item: String) -> Self { + "Sorry can't find \(item)" + } + + static func responseFailureEmptyValue(item: String) -> Self { + "Invalid empty value for \(item)" + } + + static func responseFailureInvalidValue(value: Int, item: String) -> Self { + "Invalid value \(value) for \(item) (0-100)" + } +} diff --git a/AppIntents/iOS16/SetLocationValue.swift b/AppIntents/iOS16/SetLocationValue.swift new file mode 100644 index 000000000..8ccc106c8 --- /dev/null +++ b/AppIntents/iOS16/SetLocationValue.swift @@ -0,0 +1,110 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetLocationValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidLatitude + case invalidLongitude + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case .invalidLatitude: + "Latitude must be between -90 and 90" + case .invalidLongitude: + "Longitude must be between -180 and 180" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetLocationValueIntent for iOS 17+") +struct SetLocationValue: AppIntent, PredictableIntent { + struct LocationOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .location } + return items.map(\.name) + } + } + + static let title: LocalizedStringResource = "Set Location Control Value" + static let description = IntentDescription("Set the latitude and longitude of a location control item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: LocationOptionsProvider()) + var item: String + + @Parameter(title: "Latitude") + var latitude: Double + + @Parameter(title: "Longitude") + var longitude: Double + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$latitude), \(\.$longitude)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$latitude, \.$longitude, \.$home)) { item, latitude, longitude, _ in + DisplayRepresentation( + title: "Set \(item) to \(latitude), \(longitude)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.location] + ) + + guard (-90 ... 90).contains(latitude) else { + throw SetLocationValueError.invalidLatitude + } + + guard (-180 ... 180).contains(longitude) else { + throw SetLocationValueError.invalidLongitude + } + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetLocationValueError.itemNotFound(item) + } + + let openHABItem = items[0] + let command = "\(latitude),\(longitude)" + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: command) + } catch { + throw SetLocationValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent location \(latitude), \(longitude) to \(item)") + } +} diff --git a/AppIntents/iOS16/SetNumberValue.swift b/AppIntents/iOS16/SetNumberValue.swift new file mode 100644 index 000000000..8ecb385a9 --- /dev/null +++ b/AppIntents/iOS16/SetNumberValue.swift @@ -0,0 +1,128 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetNumberValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetNumberValueIntent for iOS 17+") +struct SetNumberValue: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct StringOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .number } + return items.map(\.name) + } + } + + static let intentClassName = "OpenHABSetNumberValueIntent" + + static let title: LocalizedStringResource = "Set Number Control Value" + static let description = IntentDescription("Set the decimal value of a number control item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: StringOptionsProvider()) + var item: String + + @Parameter(title: "Value") + var value: Double + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$value)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$value, \.$home)) { item, value, _ in + DisplayRepresentation( + title: "Set \(item) to \(value)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.number, .numberWithDimension] + ) + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetNumberValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: String(value)) + } catch { + throw SetNumberValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(value: value, item: item)) + } +} + +private extension IntentDialog { + static var itemParameterConfiguration: Self { + "Number Item Name" + } + + static var homeParameterConfiguration: Self { + "Home name" + } + + static var homeParameterDisambiguationSelection: Self { + "For which home do you want to get the value?" + } + + static func homeParameterDisambiguationIntro(count: Int, item: String) -> Self { + "There are \(count) configured homes with an item named '\(item)'." + } + + static func homeParameterConfirmation(home: Home) -> Self { + "Just to confirm, you wanted '\(home)'?" + } + + static func responseSuccess(value: Double, item: String) -> Self { + "Sent the number \(value) to \(item)" + } + + static func responseFailureInvalidItem(item: String) -> Self { + "Sorry can't find \(item)" + } + + static func responseFailureEmptyValue(item: String) -> Self { + "Invalid empty value for \(item)" + } +} diff --git a/AppIntents/iOS16/SetPlayerValue.swift b/AppIntents/iOS16/SetPlayerValue.swift new file mode 100644 index 000000000..c18ee650c --- /dev/null +++ b/AppIntents/iOS16/SetPlayerValue.swift @@ -0,0 +1,116 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +@available(iOS, introduced: 16.0, obsoleted: 17.0) +enum SetPlayerValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidAction(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .invalidAction(action, itemName): + "Action invalid: \(action) for \(itemName)" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetPlayerValueIntent for iOS 17+") +struct SetPlayerValue: AppIntent, PredictableIntent { + // swiftlint:disable type_contents_order + + static let title: LocalizedStringResource = "Set Player Control Value" + static let description = IntentDescription("Send a player command such as play, pause, next, or previous") + + @Parameter(title: "Home") + var home: Home? + + struct ItemOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .player } + return items.map(\.name) + } + } + + @Parameter(title: "Item", optionsProvider: ItemOptionsProvider()) + var item: String + + struct ActionOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + ["Play", "Pause", "Next", "Previous", "Rewind", "Fast Forward"] + } + } + + @Parameter(title: "Action", optionsProvider: ActionOptionsProvider()) + var action: String + + static var parameterSummary: some ParameterSummary { + Summary("Send \(\.$action) to \(\.$item)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$action, \.$home)) { item, action, _ in + DisplayRepresentation( + title: "Send \(action) to \(item)", + subtitle: "" + ) + } + } + + private static let actionMap: [String: String] = [ + "Play": "PLAY", + "Pause": "PAUSE", + "Next": "NEXT", + "Previous": "PREVIOUS", + "Rewind": "REWIND", + "Fast Forward": "FASTFORWARD" + ] + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.player] + ) + + guard let command = Self.actionMap[action] else { + throw SetPlayerValueError.invalidAction(action, item) + } + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetPlayerValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: command) + } catch { + throw SetPlayerValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: "Sent the action of \(action) to player \(item)") + } +} diff --git a/AppIntents/iOS16/SetStringValue.swift b/AppIntents/iOS16/SetStringValue.swift new file mode 100644 index 000000000..cba35efa2 --- /dev/null +++ b/AppIntents/iOS16/SetStringValue.swift @@ -0,0 +1,128 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +enum SetStringValueError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SetStringValueIntent for iOS 17+") +struct SetStringValue: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + struct StringOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .stringItem } + return items.map(\.name) + } + } + + static let intentClassName = "OpenHABSetStringValueIntent" + + static let title: LocalizedStringResource = "Set String Control Value" + static let description = IntentDescription("Set the string of a string control item") + + // swiftlint:disable type_contents_order + @Parameter(title: "Item", optionsProvider: StringOptionsProvider()) + var item: String + + @Parameter(title: "Value") + var value: String + + @Parameter(title: "Home") + var home: Home? + + static var parameterSummary: some ParameterSummary { + Summary("Set \(\.$item) to \(\.$value)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$value, \.$home)) { item, value, _ in + DisplayRepresentation( + title: "Set \(item) to \(value)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.stringItem] + ) + + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetStringValueError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: value) + } catch { + throw SetStringValueError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(value: value, item: item)) + } +} + +private extension IntentDialog { + static var itemParameterConfiguration: Self { + "String Item Name" + } + + static var homeParameterConfiguration: Self { + "Home name" + } + + static var homeParameterDisambiguationSelection: Self { + "For which home do you want to get the value?" + } + + static func homeParameterDisambiguationIntro(count: Int, item: String) -> Self { + "There are \(count) configured homes with an item named '\(item)'." + } + + static func homeParameterConfirmation(home: Home) -> Self { + "Just to confirm, you wanted '\(home)'?" + } + + static func responseSuccess(value: String, item: String) -> Self { + "Sent the string \(value) to \(item)" + } + + static func responseFailureInvalidItem(item: String) -> Self { + "Sorry can't find \(item)" + } + + static func responseFailureEmptyValue(item: String) -> Self { + "Invalid empty value for \(item)" + } +} diff --git a/AppIntents/iOS16/SetSwitchState.swift b/AppIntents/iOS16/SetSwitchState.swift new file mode 100644 index 000000000..ccea51cf5 --- /dev/null +++ b/AppIntents/iOS16/SetSwitchState.swift @@ -0,0 +1,120 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +import OpenHABCore + +@available(iOS, introduced: 16.0, obsoleted: 17.0) +enum SetSwitchStateError: Error, CustomLocalizedStringResourceConvertible { + case itemNotFound(String) + case invalidAction(String, String) + case itemNotInHome(String, String) + case commandFailed(String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case let .itemNotFound(itemName): + "Item '\(itemName)' not found" + case let .invalidAction(action, itemName): + "Action invalid: \(action) for \(itemName)" + case let .itemNotInHome(itemName, homeName): + "Item '\(itemName)' is not in home '\(homeName)'" + case let .commandFailed(message): + "Command failed: \(message)" + } + } +} + +// @available(iOS, introduced: 16.0, obsoleted: 17.0, message: "Use SwitchStateIntent for iOS 17+") +struct SetSwitchState: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { + // swiftlint:disable type_contents_order + + static let intentClassName = "OpenHABSetSwitchStateIntent" + static let title: LocalizedStringResource = "Set Switch State" + static let description = IntentDescription("Set the state of a switch on or off") + + @Parameter(title: "Home") + var home: Home? + + struct ItemOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + let allItems = await OpenHABItemCache.instance.getAllCachedItems() + let items = allItems.flatMap(\.value).filter { $0.type == .switchItem } + return items.map(\.name) + } + } + + @Parameter(title: "Item", optionsProvider: ItemOptionsProvider()) + var item: String + + struct ActionOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [String] { + ActionMapper.onOffOptions + } + } + + @Parameter(title: "Action", optionsProvider: ActionOptionsProvider()) + var action: String + + static var parameterSummary: some ParameterSummary { + Summary("Send \(\.$action) to \(\.$item)") { + \.$home + } + } + + // swiftlint:enable type_contents_order + + static var predictionConfiguration: some IntentPredictionConfiguration { + IntentPrediction(parameters: (\.$item, \.$action, \.$home)) { item, action, _ in + DisplayRepresentation( + title: "Send \(action) to \(item)", + subtitle: "" + ) + } + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let homeId = try await HomeResolver.resolveHomeId( + selectedHome: home, + itemName: item, + allowedTypes: [.switchItem] + ) + + guard let command = ActionMapper.command(from: action) else { + throw SetSwitchStateError.invalidAction(action, item) + } + guard let items = await OpenHABItemCache.instance.getCachedItem(name: item, home: homeId), + !items.isEmpty else { + throw SetSwitchStateError.itemNotFound(item) + } + + let openHABItem = items[0] + + do { + try await OpenHABItemCache.instance.sendCommand(to: openHABItem, home: homeId, command: command) + } catch { + throw SetSwitchStateError.commandFailed(error.localizedDescription) + } + + return .result(dialog: .responseSuccess(action: action, item: item)) + } +} + +private extension IntentDialog { + static func responseSuccess(action: String, item: String) -> Self { + "Sent the action of \(action) to switch \(item)" + } + + static func responseFailureInvalidAction(action: String, item: String) -> Self { + "Action invalid: \(action) for \(item)" + } +} diff --git a/CommonUI/Sources/CommonUI/OHTextTokenStyle.swift b/CommonUI/Sources/CommonUI/OHTextTokenStyle.swift index 97551d17f..be9c5a26a 100644 --- a/CommonUI/Sources/CommonUI/OHTextTokenStyle.swift +++ b/CommonUI/Sources/CommonUI/OHTextTokenStyle.swift @@ -23,7 +23,7 @@ public enum OHTextToken { } public enum OHAccessibilityToken { - public static let minimumHitTarget: CGFloat = 32 + public static let minimumHitTarget: CGFloat = 44 } private struct OHTextTokenModifier: ViewModifier { diff --git a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift index ced139a2e..2eb54f84a 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift @@ -13,10 +13,35 @@ import Combine import Foundation import os.log +public enum OpenHABItemCacheError: Error, LocalizedError { + case homeNotReachable(UUID) + case commandFailed(any Error) + case stateFailed(any Error) + + public var errorDescription: String? { + switch self { + case let .homeNotReachable(homeId): + "Home \(homeId) is not reachable" + case let .commandFailed(error): + "Could not send command: \(error.localizedDescription)" + case let .stateFailed(error): + "Could not send state: \(error.localizedDescription)" + } + } +} + public actor OpenHABItemCache { public static let instance = OpenHABItemCache() private static let networkTimeout: TimeInterval = 5 + private static let stubsDefaultsKey = "openHABItemStubs" + private static let sharedDefaultsSuiteName = "group.org.openhab.app" + + private struct ItemStub: Codable { + let name: String + let label: String + let type: String + } private var networkTrackers: [UUID: NetworkTracker] = [:] @@ -38,7 +63,9 @@ public actor OpenHABItemCache { public func getCachedItem(name: String, home: UUID) async -> [OpenHABItem]? { await reloadCacheIfNeeded(homes: [home]) - return items[home]?.filter { $0.name == name } + let live = items[home]?.filter { $0.name == name } + if let live, !live.isEmpty { return live } + return persistedItem(name: name, home: home).map { [$0] } } public func getItemUncached(name: String, home: UUID) async -> OpenHABItem? { @@ -49,29 +76,67 @@ public actor OpenHABItemCache { return try? await networkTracker.getItemByName(id: name) } - public func sendCommand(to item: OpenHABItem, home: UUID, command: String) async { + public func sendCommand(to item: OpenHABItem, home: UUID, command: String) async throws { guard let networkTracker = await assureNetworkTracker(homeId: home) else { Logger.itemCache.error("Home \(home) not reachable") - return + throw OpenHABItemCacheError.homeNotReachable(home) } do { try await networkTracker.send(to: item, command: command) } catch { Logger.itemCache.info("Could not send command: \(error.localizedDescription)") + throw OpenHABItemCacheError.commandFailed(error) } } - public func sendState(_ item: OpenHABItem, home: UUID, state: String) async { + public func sendState(_ item: OpenHABItem, home: UUID, state: String) async throws { guard let networkTracker = await assureNetworkTracker(homeId: home) else { Logger.itemCache.error("Home \(home) not reachable") - return + throw OpenHABItemCacheError.homeNotReachable(home) } do { try await networkTracker.updateState(item: item, state: state) } catch { Logger.itemCache.info("Could not send state: \(error.localizedDescription)") + throw OpenHABItemCacheError.stateFailed(error) + } + } + + private func persistStubs() { + var allStubs: [String: [ItemStub]] = [:] + for (homeId, homeItems) in items { + allStubs[homeId.uuidString] = homeItems.map { ItemStub(name: $0.name, label: $0.label, type: $0.type?.rawValue ?? "") } + } + guard let data = try? JSONEncoder().encode(allStubs) else { return } + UserDefaults(suiteName: Self.sharedDefaultsSuiteName)?.set(data, forKey: Self.stubsDefaultsKey) + } + + private func persistedItem(name: String, home: UUID) -> OpenHABItem? { + guard let data = UserDefaults(suiteName: Self.sharedDefaultsSuiteName)?.data(forKey: Self.stubsDefaultsKey), + let allStubs = try? JSONDecoder().decode([String: [ItemStub]].self, from: data), + let stub = allStubs[home.uuidString]?.first(where: { $0.name == name }) else { return nil } + return OpenHABItem(name: stub.name, type: stub.type, state: nil, link: "", label: stub.label, groupType: nil, stateDescription: nil, commandDescription: nil, members: [], category: nil, options: nil) + } + + private func persistedItems(home: UUID) -> [OpenHABItem] { + guard let data = UserDefaults(suiteName: Self.sharedDefaultsSuiteName)?.data(forKey: Self.stubsDefaultsKey), + let allStubs = try? JSONDecoder().decode([String: [ItemStub]].self, from: data) else { return [] } + return (allStubs[home.uuidString] ?? []).map { + OpenHABItem( + name: $0.name, + type: $0.type, + state: nil, + link: "", + label: $0.label, + groupType: nil, + stateDescription: nil, + commandDescription: nil, + members: [], + category: nil, + options: nil + ) } } @@ -89,6 +154,7 @@ public actor OpenHABItemCache { homes.forEach { items[$0] = loadedItems[$0] } let now = Date.now homes.forEach { lastLoad[$0] = now } + persistStubs() let itemCounts = items.map { ($0.key, $0.value.count) } Logger.itemCache.info("Loaded \(itemCounts) items to cache") } catch { @@ -170,12 +236,227 @@ public actor OpenHABItemCache { } } +public extension OpenHABItem { + /// Checks if the item matches the specified types filter + /// - Parameter types: Optional array of item types to match. If nil, all items match. + /// - Returns: True if the item matches the filter, false otherwise + func matches(types: [OpenHABItem.ItemType]?) -> Bool { + types == nil || (type.flatMap { types?.contains($0) } == true) + } + + func searchScore(for searchTerm: String) -> Int? { + ItemSearchRanker.score(for: self, searchTerm: searchTerm) + } +} + public extension [OpenHABItem] { func filtered(by searchTerm: String? = nil, for types: [OpenHABItem.ItemType]? = nil) -> [OpenHABItem] { - // TODO: maybe allow home name for filtering and fuzzier search - filter { - (searchTerm == nil || $0.name.contains(searchTerm.orEmpty)) && - (types == nil || ($0.type != nil && types!.contains($0.type!))) + ranked(searchTerm: searchTerm, for: types).map(\.item) + } + + func ranked(searchTerm: String? = nil, for types: [OpenHABItem.ItemType]? = nil) -> [(item: OpenHABItem, score: Int)] { + compactMap { item in + guard item.matches(types: types) else { + return nil + } + + guard let searchTerm, !searchTerm.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return (item: item, score: 0) + } + + guard let score = item.searchScore(for: searchTerm) else { + return nil + } + + return (item: item, score: score) + } + .sorted { + if $0.score != $1.score { + return $0.score < $1.score + } + + return $0.item.name.localizedCaseInsensitiveCompare($1.item.name) == .orderedAscending + } + } +} + +public extension OpenHABItemCache { + func getCachedOrPersistedItem(name: String, home: UUID) async -> OpenHABItem? { + if let item = items[home]?.first(where: { $0.name == name }) { + return item + } + return persistedItem(name: name, home: home) + } + + func getCachedOrPersistedItems(types: [OpenHABItem.ItemType]?, home: UUID) async -> [OpenHABItem] { + let sourceItems = items[home] ?? persistedItems(home: home) + return sourceItems + .filtered(for: types) + .sorted(by: \.name) + } + + func getCachedOrPersistedItems(types: [OpenHABItem.ItemType]? = nil, homes: [UUID]) async -> [UUID: [OpenHABItem]] { + var result: [UUID: [OpenHABItem]] = [:] + + for homeId in homes { + let sourceItems = items[homeId] ?? persistedItems(home: homeId) + result[homeId] = sourceItems + .filtered(for: types) + .sorted(by: \.name) } + + return result + } + + func searchCachedOrPersistedItems(searchTerm: String, types: [OpenHABItem.ItemType]? = nil, homes: [UUID]? = nil) async -> [UUID: [OpenHABItem]] { + let targetHomes: [UUID] + if let homes { + targetHomes = homes + } else { + targetHomes = await Preferences.shared.listStoredHomes() + } + var result: [UUID: [OpenHABItem]] = [:] + + for homeId in targetHomes { + let sourceItems = items[homeId] ?? persistedItems(home: homeId) + let filtered = sourceItems.ranked(searchTerm: searchTerm, for: types).map(\.item) + if !filtered.isEmpty { + result[homeId] = filtered + } + } + + return result + } + + func getItemNames(searchTerm: String?, types: [OpenHABItem.ItemType]?, home: UUID) async -> [String] { + await reloadCacheIfNeeded(homes: [home]) + return items[home]? + .filtered(by: searchTerm, for: types) + .sorted(by: \.name) + .map(\.name) ?? [] + } + + func getCachedItems(types: [OpenHABItem.ItemType]?, home: UUID) async -> [OpenHABItem] { + await reloadCacheIfNeeded(homes: [home]) + return items[home]? + .filtered(for: types) + .sorted(by: \.name) ?? [] + } + + func searchItems(searchTerm: String, types: [OpenHABItem.ItemType]? = nil) async -> [UUID: [OpenHABItem]] { + let allItems = await getAllCachedItems() + var result: [UUID: [OpenHABItem]] = [:] + + for (homeId, homeItems) in allItems { + let filtered = homeItems.ranked(searchTerm: searchTerm, for: types).map(\.item) + if !filtered.isEmpty { + result[homeId] = filtered + } + } + + return result + } +} + +private enum ItemSearchRanker { + static func score(for item: OpenHABItem, searchTerm: String) -> Int? { + let tokens = normalizedTokens(in: searchTerm) + guard !tokens.isEmpty else { + return 0 + } + + let candidates = [item.name, item.label] + return candidates.enumerated().compactMap { index, candidate in + score(candidate: candidate, tokens: tokens).map { ($0 * 10) + index } + }.min() + } + + private static func score(candidate: String, tokens: [String]) -> Int? { + let normalizedCandidate = normalize(candidate) + let joinedTokens = tokens.joined() + + if normalizedCandidate == joinedTokens { + return 0 + } + + if normalizedCandidate.hasPrefix(joinedTokens) { + return 10 + } + + if let wordPrefixScore = wordPrefixScore(candidate: normalizedCandidate, tokens: tokens) { + return 20 + wordPrefixScore + } + + if let orderedTokenScore = orderedTokenScore(candidate: normalizedCandidate, tokens: tokens) { + return 40 + orderedTokenScore + } + + if tokens.allSatisfy(normalizedCandidate.contains) { + return 80 + tokens.reduce(0) { partialResult, token in + partialResult + (normalizedCandidate.distance(from: normalizedCandidate.startIndex, to: normalizedCandidate.range(of: token)?.lowerBound ?? normalizedCandidate.startIndex)) + } + } + + return nil + } + + private static func wordPrefixScore(candidate: String, tokens: [String]) -> Int? { + let words = candidate.split(whereSeparator: { !$0.isLetter && !$0.isNumber }).map(String.init) + guard !words.isEmpty, words.count >= tokens.count else { + return nil + } + + var wordIndex = 0 + var score = 0 + + for token in tokens { + var matched = false + + while wordIndex < words.count { + if words[wordIndex].hasPrefix(token) { + score += wordIndex + matched = true + wordIndex += 1 + break + } + wordIndex += 1 + } + + guard matched else { + return nil + } + } + + return score + } + + private static func orderedTokenScore(candidate: String, tokens: [String]) -> Int? { + var searchStartIndex = candidate.startIndex + var previousMatchUpperBound = candidate.startIndex + var score = 0 + + for token in tokens { + guard let range = candidate.range(of: token, range: searchStartIndex ..< candidate.endIndex) else { + return nil + } + + score += candidate.distance(from: previousMatchUpperBound, to: range.lowerBound) + previousMatchUpperBound = range.upperBound + searchStartIndex = range.upperBound + } + + return score + } + + private static func normalizedTokens(in searchTerm: String) -> [String] { + normalize(searchTerm) + .split(whereSeparator: { !$0.isLetter && !$0.isNumber }) + .map(String.init) + } + + private static func normalize(_ string: String) -> String { + string + .folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current) + .trimmingCharacters(in: .whitespacesAndNewlines) } } diff --git a/OpenHABCore/Tests/OpenHABCoreTests/OpenHABItemCacheSearchTests.swift b/OpenHABCore/Tests/OpenHABCoreTests/OpenHABItemCacheSearchTests.swift new file mode 100644 index 000000000..dfd5ab8d1 --- /dev/null +++ b/OpenHABCore/Tests/OpenHABCoreTests/OpenHABItemCacheSearchTests.swift @@ -0,0 +1,61 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +@testable import OpenHABCore +import Testing + +struct OpenHABItemCacheSearchTests { + @Test + func exactNameMatchRanksAheadOfPartialLabelMatch() { + let exactName = makeItem(name: "Light", label: "Ceiling") + let partialLabel = makeItem(name: "KitchenSwitch", label: "Kitchen Light") + + let ranked = [partialLabel, exactName].ranked(searchTerm: "Light", for: nil) + + #expect(ranked.map(\.item.name) == ["Light", "KitchenSwitch"]) + } + + @Test + func multiSubstringSearchMatchesAcrossWords() { + let kitchenSwitch = makeItem(name: "KitchenSwitch", label: "Kitchen Main Switch") + let bedroomLight = makeItem(name: "BedroomLight", label: "Bedroom Ceiling Light") + + let ranked = [bedroomLight, kitchenSwitch].ranked(searchTerm: "kit swi", for: nil) + + #expect(ranked.map(\.item.name) == ["KitchenSwitch"]) + } + + @Test + func rankedSearchHonorsTypeFilter() { + let switchItem = makeItem(name: "KitchenSwitch", label: "Kitchen Switch", type: "Switch") + let numberItem = makeItem(name: "KitchenTemperature", label: "Kitchen Temperature", type: "Number") + + let ranked = [switchItem, numberItem].ranked(searchTerm: "kitchen", for: [.switchItem]) + + #expect(ranked.map(\.item.name) == ["KitchenSwitch"]) + } + + private func makeItem(name: String, label: String, type: String = "Switch") -> OpenHABItem { + OpenHABItem( + name: name, + type: type, + state: nil, + link: "", + label: label, + groupType: nil, + stateDescription: nil, + commandDescription: nil, + members: [], + category: nil, + options: nil + ) + } +} diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index f2d7b3284..3ef988039 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -7,19 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 2F21508A2F75C2FC001BA057 /* GetItemStateIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150732F75C2FC001BA057 /* GetItemStateIntentHandler.swift */; }; - 2F21508B2F75C2FC001BA057 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150752F75C2FC001BA057 /* IntentHandler.swift */; }; - 2F21508D2F75C2FC001BA057 /* OpenHABIntentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150812F75C2FC001BA057 /* OpenHABIntentHelper.swift */; }; - 2F21508E2F75C2FC001BA057 /* SetColorValueIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150832F75C2FC001BA057 /* SetColorValueIntentHandler.swift */; }; - 2F21508F2F75C2FC001BA057 /* SetContactStateValueIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150842F75C2FC001BA057 /* SetContactStateValueIntentHandler.swift */; }; - 2F2150902F75C2FC001BA057 /* SetDimmerRollerValueIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150852F75C2FC001BA057 /* SetDimmerRollerValueIntentHandler.swift */; }; - 2F2150912F75C2FC001BA057 /* SetNumberValueIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150862F75C2FC001BA057 /* SetNumberValueIntentHandler.swift */; }; - 2F2150922F75C2FC001BA057 /* SetStringValueIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150872F75C2FC001BA057 /* SetStringValueIntentHandler.swift */; }; - 2F2150932F75C2FC001BA057 /* SetSwitchStateIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150882F75C2FC001BA057 /* SetSwitchStateIntentHandler.swift */; }; - 2F2150982F75C306001BA057 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 2F2150972F75C306001BA057 /* Intents.intentdefinition */; }; - 2F2150A22F75C434001BA057 /* Intents.intentdefinition in Resources */ = {isa = PBXBuildFile; fileRef = 2F2150972F75C306001BA057 /* Intents.intentdefinition */; }; 2F399AC92F54599400F72A30 /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 2F399AC82F54599400F72A30 /* Flow */; }; - 4D6470DA2561F935007B03FC /* openHABIntents.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4D6470D32561F935007B03FC /* openHABIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6557AF8F2C0241C10094D0C8 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6557AF8E2C0241C10094D0C8 /* PrivacyInfo.xcprivacy */; }; 6557AF902C0241C10094D0C8 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6557AF8E2C0241C10094D0C8 /* PrivacyInfo.xcprivacy */; }; 6557AF922C039D140094D0C8 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 6557AF912C039D140094D0C8 /* FirebaseMessaging */; }; @@ -34,8 +22,6 @@ 937E4488270B37A600A98C26 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 937E4487270B37A600A98C26 /* Kingfisher */; }; 937E448C270B37CA00A98C26 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 937E448B270B37CA00A98C26 /* Kingfisher */; }; 937E448E270B37D200A98C26 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 937E448D270B37D200A98C26 /* DeviceKit */; }; - 937E4492270B37FE00A98C26 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 937E4491270B37FE00A98C26 /* Kingfisher */; }; - 937E44E2270B393C00A98C26 /* OpenHABCore in Frameworks */ = {isa = PBXBuildFile; productRef = 937E44E1270B393C00A98C26 /* OpenHABCore */; }; 9397EDEC2587837000F266E1 /* openHABWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = DA0775152346705D0086C685 /* openHABWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 93F8063527AE6C620035A6B0 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 93F8063427AE6C620035A6B0 /* FirebaseCrashlytics */; }; 93F8064727AE7A050035A6B0 /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 93F8064627AE7A050035A6B0 /* SwiftMessages */; }; @@ -44,16 +30,33 @@ DA10161B2DC7BAE500552D14 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = DA10161A2DC7BAE500552D14 /* SFSafeSymbols */; }; DA28C362225241DE00AB409C /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA28C361225241DE00AB409C /* WebKit.framework */; settings = {ATTRIBUTES = (Required, ); }; }; DA2C4FD52B4F573300D1C533 /* SDWebImageSVGCoder in Frameworks */ = {isa = PBXBuildFile; productRef = DA2C4FD42B4F573300D1C533 /* SDWebImageSVGCoder */; }; + DA448E852EF435B400F0893C /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA448E772EF435B400F0893C /* Home.swift */; }; + DA4BF3522F0307A40082479C /* GetItemStateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF3512F0307A40082479C /* GetItemStateIntent.swift */; }; + DA4BF3562F03095D0082479C /* SetColorValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF3542F03095D0082479C /* SetColorValueIntent.swift */; }; + DA4BF3572F03095D0082479C /* ContactStateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF3552F03095D0082479C /* ContactStateIntent.swift */; }; + DA4BF3592F03098B0082479C /* SetDimmerRollerValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF3582F03098B0082479C /* SetDimmerRollerValueIntent.swift */; }; + DA4BF35B2F0309A60082479C /* SetNumberValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF35A2F0309A60082479C /* SetNumberValueIntent.swift */; }; + DA4BF35D2F0309B10082479C /* SetStringValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF35C2F0309B10082479C /* SetStringValueIntent.swift */; }; + DA4BF4032F06F5950082479C /* SwitchAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4022F06F5950082479C /* SwitchAction.swift */; }; + DA4BF4052F06F5CA0082479C /* ContactState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4042F06F5CA0082479C /* ContactState.swift */; }; + DA4BF4072F0800010082479C /* PlayerAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4062F0800010082479C /* PlayerAction.swift */; }; + DA4BF4092F0800020082479C /* SetPlayerValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4082F0800020082479C /* SetPlayerValueIntent.swift */; }; + DA4BF40B2F0800030082479C /* SetDateTimeValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF40A2F0800030082479C /* SetDateTimeValueIntent.swift */; }; + DA4BF40D2F0800040082479C /* SetLocationValueIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF40C2F0800040082479C /* SetLocationValueIntent.swift */; }; + DA4BF4342F0705580082479C /* ItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4332F0705580082479C /* ItemEntity.swift */; }; + DA4BF4362F07056F0082479C /* ItemEntityQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BF4352F07056F0082479C /* ItemEntityQuery.swift */; }; DA4D4DB5233F9ACB00B37E37 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = DA4D4DB4233F9ACB00B37E37 /* README.md */; }; - DA7ACD5F2DC3DB130055CFC7 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = DA7ACD5E2DC3DB130055CFC7 /* SFSafeSymbols */; settings = {ATTRIBUTES = (Required, ); }; }; DA817E7A234BF39B00C91824 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = DA817E79234BF39B00C91824 /* CHANGELOG.md */; }; DA9A7EFD2D668D5900824156 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = DA9A7EFC2D668D5900824156 /* SFSafeSymbols */; }; DA9A7EFF2D66915900824156 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = DA9A7EFE2D66915900824156 /* SFSafeSymbols */; }; DABB5E332D98972F009A4B8A /* SDWebImageSVGCoder in Frameworks */ = {isa = PBXBuildFile; productRef = DABB5E322D98972F009A4B8A /* SDWebImageSVGCoder */; }; DAC949FA2E219F0D007E67B7 /* CommonUI in Frameworks */ = {isa = PBXBuildFile; productRef = DAC949F92E219F0D007E67B7 /* CommonUI */; }; DAC949FC2E219F30007E67B7 /* CommonUI in Frameworks */ = {isa = PBXBuildFile; productRef = DAC949FB2E219F30007E67B7 /* CommonUI */; }; + DAD5E85C2F02C272003215C0 /* SetSwitchItemIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD5E85B2F02C272003215C0 /* SetSwitchItemIntent.swift */; }; + DAD5E85E2F02C3C6003215C0 /* ItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD5E85D2F02C3BC003215C0 /* ItemIdentifier.swift */; }; DAEFAA7F2F63536A00AC300B /* OpenHABCore in Frameworks */ = {isa = PBXBuildFile; productRef = DAEFAA7E2F63536A00AC300B /* OpenHABCore */; }; DAEFAA812F63537600AC300B /* SDWebImageSVGCoder in Frameworks */ = {isa = PBXBuildFile; productRef = DAEFAA802F63537600AC300B /* SDWebImageSVGCoder */; }; + DAF58C7B2EF6E99500483AFD /* SwitchItemEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF58C7A2EF6E99500483AFD /* SwitchItemEntity.swift */; }; DAFF80982E4F47830084513E /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = DAFF80972E4F47830084513E /* SDWebImage */; }; DFB2622B18830A3600D3244D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB2622A18830A3600D3244D /* Foundation.framework */; }; DFB2622D18830A3600D3244D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB2622C18830A3600D3244D /* CoreGraphics.framework */; }; @@ -62,13 +65,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 4D6470D82561F935007B03FC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DFB2621F18830A3600D3244D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4D6470D22561F935007B03FC; - remoteInfo = openHABIntents; - }; 657144532C1E438700C8A1F3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DFB2621F18830A3600D3244D /* Project object */; @@ -138,7 +134,6 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 4D6470DA2561F935007B03FC /* openHABIntents.appex in Embed Foundation Extensions */, 657144552C1E438700C8A1F3 /* NotificationService.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -167,44 +162,109 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2F2150732F75C2FC001BA057 /* GetItemStateIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetItemStateIntentHandler.swift; sourceTree = ""; }; - 2F2150742F75C2FC001BA057 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 2F2150752F75C2FC001BA057 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; - 2F2150812F75C2FC001BA057 /* OpenHABIntentHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABIntentHelper.swift; sourceTree = ""; }; - 2F2150822F75C2FC001BA057 /* openHABIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = openHABIntents.entitlements; sourceTree = ""; }; - 2F2150832F75C2FC001BA057 /* SetColorValueIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetColorValueIntentHandler.swift; sourceTree = ""; }; - 2F2150842F75C2FC001BA057 /* SetContactStateValueIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetContactStateValueIntentHandler.swift; sourceTree = ""; }; - 2F2150852F75C2FC001BA057 /* SetDimmerRollerValueIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDimmerRollerValueIntentHandler.swift; sourceTree = ""; }; - 2F2150862F75C2FC001BA057 /* SetNumberValueIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetNumberValueIntentHandler.swift; sourceTree = ""; }; - 2F2150872F75C2FC001BA057 /* SetStringValueIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetStringValueIntentHandler.swift; sourceTree = ""; }; - 2F2150882F75C2FC001BA057 /* SetSwitchStateIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetSwitchStateIntentHandler.swift; sourceTree = ""; }; - 2F2150962F75C306001BA057 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; - 2F2150992F75C325001BA057 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; - 2F21509A2F75C327001BA057 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Intents.strings; sourceTree = ""; }; - 2F21509B2F75C329001BA057 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Intents.strings; sourceTree = ""; }; - 2F21509C2F75C32B001BA057 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = ""; }; - 2F21509D2F75C32D001BA057 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Intents.strings; sourceTree = ""; }; - 2F21509E2F75C32F001BA057 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; - 2F21509F2F75C331001BA057 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = ""; }; - 2F2150A02F75C333001BA057 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Intents.strings; sourceTree = ""; }; - 2F2150A12F75C335001BA057 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = ""; }; - 4D6470D32561F935007B03FC /* openHABIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = openHABIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 1224F78D228A89FC00750965 /* WatchMessageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchMessageService.swift; sourceTree = ""; }; + 2F399AB92F5357E900F72A30 /* InputCommandFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCommandFormatterTests.swift; sourceTree = ""; }; + 399449C421544C61AD83450C /* TextRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRow.swift; sourceTree = ""; }; 6557AF8E2C0241C10094D0C8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 6571444E2C1E438700C8A1F3 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 657144502C1E438700C8A1F3 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 657144522C1E438700C8A1F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 657144972C30A3E300C8A1F3 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; 933D7F0422E7015000621A03 /* openHABUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = openHABUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 935D340A257B7DC00020A404 /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Intents.intentdefinition; path = openHABIntents/Intents.intentdefinition; sourceTree = SOURCE_ROOT; }; + 933D7F0622E7015000621A03 /* OpenHABUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABUITests.swift; sourceTree = ""; }; + 933D7F0822E7015100621A03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 933D7F0E22E7030600621A03 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; + 93685A792ADE755C0077A9A6 /* openHABTests.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = openHABTests.xctestplan; sourceTree = ""; }; + 93685A7B2ADE755C0077A9A6 /* openHABTestsSwift.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = openHABTestsSwift.xctestplan; sourceTree = ""; }; + 938BF89524EFBC5400E6B52F /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; + D86D8BF295C448039B2B85EB /* SelectionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionRow.swift; sourceTree = ""; }; + DA0749DD23E0B5950057FA83 /* ColorPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerRow.swift; sourceTree = ""; }; + DA0749DF23E0BF510057FA83 /* ColorSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSelection.swift; sourceTree = ""; }; DA0775152346705D0086C685 /* openHABWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = openHABWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DA07751A2346705F0086C685 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + DA07751C2346705F0086C685 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DA07752A2346705F0086C685 /* OpenHABWatchAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABWatchAppDelegate.swift; sourceTree = ""; }; + DA07752C2346705F0086C685 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; }; + DA07752E2346705F0086C685 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; + DA0775302346705F0086C685 /* ComplicationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = ""; }; + DA0775372346705F0086C685 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DA0775382346705F0086C685 /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; }; + DA077649234683BC0086C685 /* SwitchRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchRow.swift; sourceTree = ""; }; + DA0776EF234788010086C685 /* UserData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserData.swift; sourceTree = ""; }; DA0DA9E12E0C9B74000C5D0A /* BuildTools */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = BuildTools; sourceTree = ""; }; + DA0F37CF23D4ACC7007EAB48 /* SliderRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderRow.swift; sourceTree = ""; }; + DA15BFBC23C6726400BD8ADA /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; + DA19E25A22FD801D002F8F2F /* OpenHABGeneralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABGeneralTests.swift; sourceTree = ""; }; + DA2740FF2EA62F1F002FE576 /* SitemapPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitemapPageView.swift; sourceTree = ""; }; + DA2741012EA62FA3002FE576 /* SegmentSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentSelectionView.swift; sourceTree = ""; }; DA28C361225241DE00AB409C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + DA2D2F892F3943A800EC605A /* WidgetRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetRowViewModel.swift; sourceTree = ""; }; DA2DC22F21F2736C00830730 /* openHABTestsSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = openHABTestsSwift.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DA2DC23321F2736C00830730 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DA2E0AA323DC96E9009B0A99 /* ImageWithAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWithAction.swift; sourceTree = ""; }; + DA2E0B0D23DCC152009B0A99 /* MapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; + DA2E0B0F23DCC439009B0A99 /* MapViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewRow.swift; sourceTree = ""; }; + DA32D1B32C8C98C40018D974 /* IconWithAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconWithAction.swift; sourceTree = ""; }; + DA448E772EF435B400F0893C /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; + DA4BF3512F0307A40082479C /* GetItemStateIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetItemStateIntent.swift; sourceTree = ""; }; + DA4BF3542F03095D0082479C /* SetColorValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetColorValueIntent.swift; sourceTree = ""; }; + DA4BF3552F03095D0082479C /* ContactStateIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactStateIntent.swift; sourceTree = ""; }; + DA4BF3582F03098B0082479C /* SetDimmerRollerValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDimmerRollerValueIntent.swift; sourceTree = ""; }; + DA4BF35A2F0309A60082479C /* SetNumberValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetNumberValueIntent.swift; sourceTree = ""; }; + DA4BF35C2F0309B10082479C /* SetStringValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetStringValueIntent.swift; sourceTree = ""; }; + DA4BF4022F06F5950082479C /* SwitchAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchAction.swift; sourceTree = ""; }; + DA4BF4042F06F5CA0082479C /* ContactState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactState.swift; sourceTree = ""; }; + DA4BF4062F0800010082479C /* PlayerAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerAction.swift; sourceTree = ""; }; + DA4BF4082F0800020082479C /* SetPlayerValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetPlayerValueIntent.swift; sourceTree = ""; }; + DA4BF40A2F0800030082479C /* SetDateTimeValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDateTimeValueIntent.swift; sourceTree = ""; }; + DA4BF40C2F0800040082479C /* SetLocationValueIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetLocationValueIntent.swift; sourceTree = ""; }; + DA4BF4332F0705580082479C /* ItemEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEntity.swift; sourceTree = ""; }; + DA4BF4352F07056F0082479C /* ItemEntityQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEntityQuery.swift; sourceTree = ""; }; DA4D4DB4233F9ACB00B37E37 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; DA4D4E0E2340A00200B37E37 /* Changes.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Changes.md; sourceTree = ""; }; + DA65871E236F83CD007E2E7F /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = ""; }; + DA72E1B5236DEA0900B8EF3A /* AppMessageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageService.swift; sourceTree = ""; }; DA817E79234BF39B00C91824 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; DA83C81D2F48AF7600CDACED /* Version.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Version.xcconfig; sourceTree = ""; }; + DA8B14B52F3A0DFF007753FD /* WidgetRowFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetRowFactory.swift; sourceTree = ""; }; + DA8B14B72F3A11F0007753FD /* PreviewWidgetFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWidgetFactory.swift; sourceTree = ""; }; + DA8B14B92F3A373A007753FD /* PreviewNavigationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewNavigationContainer.swift; sourceTree = ""; }; + DA8B14BB2F3A3CB5007753FD /* WatchTypography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchTypography.swift; sourceTree = ""; }; DA8B15512F3BB74B007753FD /* openHABWatchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = openHABWatchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DA9641592F292EE200CEC181 /* BonjourDiscoveryViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BonjourDiscoveryViewModelTests.swift; sourceTree = ""; }; + DA96415B2F292F0600CEC181 /* OpenHABEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABEndPoint.swift; sourceTree = ""; }; + DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsBacked.swift; sourceTree = ""; }; + DABB4CD22F45EFD100111AF3 /* openHABWatch.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = openHABWatch.xctestplan; path = TestPlans/openHABWatch.xctestplan; sourceTree = ""; }; + DABB4CDD2F4746B100111AF3 /* SitemapRowInputMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitemapRowInputMapperTests.swift; sourceTree = ""; }; + DABB4CF12F4790AD00111AF3 /* RowLayoutPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowLayoutPolicyTests.swift; sourceTree = ""; }; + DABB4CF32F47BC5100111AF3 /* SliderOverrideSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderOverrideSyncTests.swift; sourceTree = ""; }; + DAC6608B236F6F4200F4501E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + DAC6608C236F771600F4501E /* PreferencesSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesSwiftUIView.swift; sourceTree = ""; }; + DAC9AF4624F9669F006DAE93 /* OpenHABWidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABWidgetExtension.swift; sourceTree = ""; }; + DAC9AF4824F966FA006DAE93 /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; DACC0F7B2E883AC700B62043 /* AGENTS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = AGENTS.md; sourceTree = ""; }; DAD0856C2AE4782B001D36BE /* openHABWatchSwiftUI Watch AppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "openHABWatchSwiftUI Watch AppTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DAD085762AE4782E001D36BE /* openHABWatchSwiftUI Watch AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "openHABWatchSwiftUI Watch AppUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DAD0857A2AE4782F001D36BE /* OpenHABWatchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABWatchUITests.swift; sourceTree = ""; }; + DAD0857C2AE4782F001D36BE /* OpenHABWatchLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABWatchLaunchTests.swift; sourceTree = ""; }; + DAD5E85B2F02C272003215C0 /* SetSwitchItemIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetSwitchItemIntent.swift; sourceTree = ""; }; + DAD5E85D2F02C3BC003215C0 /* ItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemIdentifier.swift; sourceTree = ""; }; + DAF231D127BB6EEA00AB916C /* OpenHABSVGTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABSVGTests.swift; sourceTree = ""; }; + DAF231D527BB702400AB916C /* valid_xmlns.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = valid_xmlns.svg; sourceTree = ""; }; + DAF231D627BB702500AB916C /* invalid_xmlns.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = invalid_xmlns.svg; sourceTree = ""; }; + DAF231DA27BB828000AB916C /* pantryUseTagPoints2NonExistentElement.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = pantryUseTagPoints2NonExistentElement.svg; sourceTree = ""; }; + DAF231E227BBD1A000AB916C /* embeddedpng_valid.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = embeddedpng_valid.svg; sourceTree = ""; }; + DAF4578123D630C70018B495 /* WatchIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchIconView.swift; sourceTree = ""; }; + DAF4578823D79AA50018B495 /* DetailTextLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailTextLabelView.swift; sourceTree = ""; }; + DAF4579F23DA3E1C0018B495 /* SegmentRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentRow.swift; sourceTree = ""; }; + DAF457A123DB6C640018B495 /* RollershutterRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollershutterRow.swift; sourceTree = ""; }; + DAF457A523DB9CE00018B495 /* SetpointRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetpointRow.swift; sourceTree = ""; }; + DAF457A823DBA4990018B495 /* FrameRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameRow.swift; sourceTree = ""; }; + DAF4581523DC483F0018B495 /* GenericRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericRow.swift; sourceTree = ""; }; + DAF4581723DC4A050018B495 /* ImageRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRow.swift; sourceTree = ""; }; + DAF4581D23DC60020018B495 /* ImageRawRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRawRow.swift; sourceTree = ""; }; + DAF58C7A2EF6E99500483AFD /* SwitchItemEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchItemEntity.swift; sourceTree = ""; }; + DAFAF36E2F8892A3003E60EF /* AppIntentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentsTests.swift; sourceTree = ""; }; DAFD2FE62E0D96700059A1EB /* OsLogRewriter */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = OsLogRewriter; sourceTree = ""; }; DFB2622718830A3600D3244D /* openHAB.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = openHAB.app; sourceTree = BUILT_PRODUCTS_DIR; }; DFB2622A18830A3600D3244D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -212,6 +272,7 @@ DFB2622E18830A3600D3244D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; DFB2624C18830A3600D3244D /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; DFE10413197415F900D94943 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + E1AA00042F5D000100000001 /* ScreenSaverLayoutCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSaverLayoutCalculatorTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -269,12 +330,12 @@ ); target = DA0775142346705D0086C685 /* openHABWatch */; }; - 3AE8ADF52F6849AA00AA4B6A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + DA4BF4322F0705070082479C /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - "Supporting Files/Localizable.xcstrings", + ActionMapper.swift, ); - target = 4D6470D22561F935007B03FC /* openHABIntents */; + target = DFB2622618830A3600D3244D /* openHAB */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -282,12 +343,12 @@ 2FD4E1E22F66EF0200EBB340 /* fastlane */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = fastlane; sourceTree = ""; }; 2FD4E1E62F66EF4800EBB340 /* NotificationService */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E1E82F66EF4800EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = NotificationService; sourceTree = ""; }; 2FD4E1EB2F66EF4F00EBB340 /* openHABWatchSwiftUI Watch AppUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "openHABWatchSwiftUI Watch AppUITests"; sourceTree = ""; }; - 2FD4E1EF2F66EF5500EBB340 /* openHABIntentsTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = openHABIntentsTests; sourceTree = ""; }; 2FD4E29C2F66F9A500EBB340 /* openHABWatch */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E2CA2F66F9A500EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = openHABWatch; sourceTree = ""; }; 2FD4E2CD2F66F9F600EBB340 /* openHABUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E2CF2F66F9F700EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = openHABUITests; sourceTree = ""; }; 2FD4E2E02F66F9FE00EBB340 /* openHABTestsSwift */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E2F02F66F9FE00EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = openHABTestsSwift; sourceTree = ""; }; 2FD4E2F42F66FA2900EBB340 /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E2F62F66FA2900EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; - 2FD4E52B2F670BA500EBB340 /* openHAB */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E58D2F670BA500EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 2FD4E58E2F670BA600EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 3AE8ADF52F6849AA00AA4B6A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = openHAB; sourceTree = ""; }; + 2FD4E52B2F670BA500EBB340 /* openHAB */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (2FD4E58D2F670BA500EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 2FD4E58E2F670BA600EBB340 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = openHAB; sourceTree = ""; }; + DA4BF3532F0307D30082479C /* iOS16 */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (DA4BF4322F0705070082479C /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = iOS16; sourceTree = ""; }; DA8B15522F3BB74B007753FD /* openHABWatchTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = openHABWatchTests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -308,16 +369,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4D6470D02561F935007B03FC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DA7ACD5F2DC3DB130055CFC7 /* SFSafeSymbols in Frameworks */, - 937E4492270B37FE00A98C26 /* Kingfisher in Frameworks */, - 937E44E2270B393C00A98C26 /* OpenHABCore in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6571444B2C1E438700C8A1F3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -392,58 +443,263 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2F2150892F75C2FC001BA057 /* openHABIntents */ = { + 1224F7C8228A8EC600750965 /* Domain */ = { + isa = PBXGroup; + children = ( + DA0776EF234788010086C685 /* UserData.swift */, + ); + path = Domain; + sourceTree = ""; + }; + 1224F7C9228A8ED100750965 /* External */ = { + isa = PBXGroup; + children = ( + DA72E1B5236DEA0900B8EF3A /* AppMessageService.swift */, + ); + name = External; + path = openHABWatch/External; + sourceTree = SOURCE_ROOT; + }; + 2F2150892F75C2FC001BA057 /* Watch */ = { + isa = PBXGroup; + children = ( + 1224F78D228A89FC00750965 /* WatchMessageService.swift */, + ); + name = Watch; + sourceTree = ""; + }; + 6571444F2C1E438700C8A1F3 /* NotificationService */ = { + isa = PBXGroup; + children = ( + 657144972C30A3E300C8A1F3 /* NotificationService.entitlements */, + 657144502C1E438700C8A1F3 /* NotificationService.swift */, + 657144522C1E438700C8A1F3 /* Info.plist */, + ); + path = NotificationService; + sourceTree = ""; + }; + 933D7F0522E7015000621A03 /* openHABUITests */ = { + isa = PBXGroup; + children = ( + 933D7F0E22E7030600621A03 /* SnapshotHelper.swift */, + 933D7F0622E7015000621A03 /* OpenHABUITests.swift */, + 933D7F0822E7015100621A03 /* Info.plist */, + ); + path = openHABUITests; + sourceTree = ""; + }; + 93685A782ADE755C0077A9A6 /* TestPlans */ = { + isa = PBXGroup; + children = ( + 93685A792ADE755C0077A9A6 /* openHABTests.xctestplan */, + 93685A7B2ADE755C0077A9A6 /* openHABTestsSwift.xctestplan */, + ); + path = TestPlans; + sourceTree = ""; + }; + 93F38D4623803731001B1451 /* Model */ = { + isa = PBXGroup; + children = ( + DA15BFBC23C6726400BD8ADA /* AppSettings.swift */, + DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */, + DAC9AF4624F9669F006DAE93 /* OpenHABWidgetExtension.swift */, + DAC9AF4824F966FA006DAE93 /* LazyView.swift */, + DA8B14B52F3A0DFF007753FD /* WidgetRowFactory.swift */, + DA2D2F892F3943A800EC605A /* WidgetRowViewModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + DA0775162346705D0086C685 /* openHABWatch */ = { + isa = PBXGroup; + children = ( + DA0775252346705F0086C685 /* Extension */, + 1224F7C9228A8ED100750965 /* External */, + 1224F7C8228A8EC600750965 /* Domain */, + 93F38D4623803731001B1451 /* Model */, + DA658720236F841F007E2E7F /* Views */, + DAD0858A2AE52126001D36BE /* Preview Content */, + DA07751A2346705F0086C685 /* Assets.xcassets */, + DA07751C2346705F0086C685 /* Info.plist */, + ); + path = openHABWatch; + sourceTree = ""; + }; + DA0775252346705F0086C685 /* Extension */ = { + isa = PBXGroup; + children = ( + DA07752A2346705F0086C685 /* OpenHABWatchAppDelegate.swift */, + DA07752C2346705F0086C685 /* NotificationController.swift */, + DA07752E2346705F0086C685 /* NotificationView.swift */, + DA0775302346705F0086C685 /* ComplicationController.swift */, + DAC6608B236F6F4200F4501E /* Assets.xcassets */, + DA0775372346705F0086C685 /* Info.plist */, + DA65871E236F83CD007E2E7F /* UserDefaultsExtension.swift */, + DA0775382346705F0086C685 /* PushNotificationPayload.apns */, + ); + path = Extension; + sourceTree = ""; + }; + DA2DC23021F2736C00830730 /* openHABTestsSwift */ = { + isa = PBXGroup; + children = ( + DAFAF36E2F8892A3003E60EF /* AppIntentsTests.swift */, + DA9641592F292EE200CEC181 /* BonjourDiscoveryViewModelTests.swift */, + DA2DC23321F2736C00830730 /* Info.plist */, + 938BF89524EFBC5400E6B52F /* LocalizationTests.swift */, + DA96415B2F292F0600CEC181 /* OpenHABEndPoint.swift */, + DA19E25A22FD801D002F8F2F /* OpenHABGeneralTests.swift */, + DAF231D127BB6EEA00AB916C /* OpenHABSVGTests.swift */, + DAF231D327BB6F5C00AB916C /* Ressources */, + DABB4CF12F4790AD00111AF3 /* RowLayoutPolicyTests.swift */, + E1AA00042F5D000100000001 /* ScreenSaverLayoutCalculatorTests.swift */, + DABB4CDD2F4746B100111AF3 /* SitemapRowInputMapperTests.swift */, + DABB4CF32F47BC5100111AF3 /* SliderOverrideSyncTests.swift */, + 2F399AB92F5357E900F72A30 /* InputCommandFormatterTests.swift */, + ); + path = openHABTestsSwift; + sourceTree = ""; + }; + DA448E7E2EF435B400F0893C /* AppIntents */ = { + isa = PBXGroup; + children = ( + DA4BF3652F0419F30082479C /* Intents */, + DA4BF3532F0307D30082479C /* iOS16 */, + DAD5E85D2F02C3BC003215C0 /* ItemIdentifier.swift */, + DA4BF4042F06F5CA0082479C /* ContactState.swift */, + DAF58C7A2EF6E99500483AFD /* SwitchItemEntity.swift */, + DA4BF4352F07056F0082479C /* ItemEntityQuery.swift */, + DA4BF4332F0705580082479C /* ItemEntity.swift */, + DA4BF4022F06F5950082479C /* SwitchAction.swift */, + DA4BF4062F0800010082479C /* PlayerAction.swift */, + DA448E772EF435B400F0893C /* Home.swift */, + ); + path = AppIntents; + sourceTree = ""; + }; + DA4BF3652F0419F30082479C /* Intents */ = { + isa = PBXGroup; + children = ( + DA4BF3542F03095D0082479C /* SetColorValueIntent.swift */, + DA4BF3552F03095D0082479C /* ContactStateIntent.swift */, + DA4BF3582F03098B0082479C /* SetDimmerRollerValueIntent.swift */, + DA4BF3512F0307A40082479C /* GetItemStateIntent.swift */, + DAD5E85B2F02C272003215C0 /* SetSwitchItemIntent.swift */, + DA4BF35C2F0309B10082479C /* SetStringValueIntent.swift */, + DA4BF35A2F0309A60082479C /* SetNumberValueIntent.swift */, + DA4BF4082F0800020082479C /* SetPlayerValueIntent.swift */, + DA4BF40A2F0800030082479C /* SetDateTimeValueIntent.swift */, + DA4BF40C2F0800040082479C /* SetLocationValueIntent.swift */, + ); + path = Intents; + sourceTree = ""; + }; + DA658720236F841F007E2E7F /* Views */ = { + isa = PBXGroup; + children = ( + DA2740FF2EA62F1F002FE576 /* SitemapPageView.swift */, + DAC6608C236F771600F4501E /* PreferencesSwiftUIView.swift */, + DAF457A323DB7A820018B495 /* Rows */, + DAF457A723DBA2C40018B495 /* Utils */, + ); + path = Views; + sourceTree = ""; + }; + DAD085792AE4782F001D36BE /* openHABWatchSwiftUI Watch AppUITests */ = { + isa = PBXGroup; + children = ( + DAD0857A2AE4782F001D36BE /* OpenHABWatchUITests.swift */, + DAD0857C2AE4782F001D36BE /* OpenHABWatchLaunchTests.swift */, + ); + path = "openHABWatchSwiftUI Watch AppUITests"; + sourceTree = ""; + }; + DAD0858A2AE52126001D36BE /* Preview Content */ = { isa = PBXGroup; children = ( - 2F2150732F75C2FC001BA057 /* GetItemStateIntentHandler.swift */, - 2F2150742F75C2FC001BA057 /* Info.plist */, - 2F2150752F75C2FC001BA057 /* IntentHandler.swift */, - 2F2150972F75C306001BA057 /* Intents.intentdefinition */, - 2F2150812F75C2FC001BA057 /* OpenHABIntentHelper.swift */, - 2F2150822F75C2FC001BA057 /* openHABIntents.entitlements */, - 2F2150832F75C2FC001BA057 /* SetColorValueIntentHandler.swift */, - 2F2150842F75C2FC001BA057 /* SetContactStateValueIntentHandler.swift */, - 2F2150852F75C2FC001BA057 /* SetDimmerRollerValueIntentHandler.swift */, - 2F2150862F75C2FC001BA057 /* SetNumberValueIntentHandler.swift */, - 2F2150872F75C2FC001BA057 /* SetStringValueIntentHandler.swift */, - 2F2150882F75C2FC001BA057 /* SetSwitchStateIntentHandler.swift */, - ); - path = openHABIntents; + DA8B14B92F3A373A007753FD /* PreviewNavigationContainer.swift */, + DA8B14B72F3A11F0007753FD /* PreviewWidgetFactory.swift */, + ); + path = "Preview Content"; sourceTree = ""; }; - 2FD4E92D2F67571C00EBB340 /* Recovered References */ = { + DAF231D327BB6F5C00AB916C /* Ressources */ = { isa = PBXGroup; children = ( - 935D340A257B7DC00020A404 /* Intents.intentdefinition */, + DAF231DA27BB828000AB916C /* pantryUseTagPoints2NonExistentElement.svg */, + DAF231D627BB702500AB916C /* invalid_xmlns.svg */, + DAF231D527BB702400AB916C /* valid_xmlns.svg */, + DAF231E227BBD1A000AB916C /* embeddedpng_valid.svg */, ); - name = "Recovered References"; + path = Ressources; + sourceTree = ""; + }; + DAF457A323DB7A820018B495 /* Rows */ = { + isa = PBXGroup; + children = ( + DA077649234683BC0086C685 /* SwitchRow.swift */, + DA0F37CF23D4ACC7007EAB48 /* SliderRow.swift */, + DAF457A123DB6C640018B495 /* RollershutterRow.swift */, + DAF4579F23DA3E1C0018B495 /* SegmentRow.swift */, + D86D8BF295C448039B2B85EB /* SelectionRow.swift */, + DA2741012EA62FA3002FE576 /* SegmentSelectionView.swift */, + DAF457A523DB9CE00018B495 /* SetpointRow.swift */, + DAF457A823DBA4990018B495 /* FrameRow.swift */, + DAF4581523DC483F0018B495 /* GenericRow.swift */, + 399449C421544C61AD83450C /* TextRow.swift */, + DAF4581723DC4A050018B495 /* ImageRow.swift */, + DAF4581D23DC60020018B495 /* ImageRawRow.swift */, + DA2E0B0F23DCC439009B0A99 /* MapViewRow.swift */, + DA0749DD23E0B5950057FA83 /* ColorPickerRow.swift */, + ); + path = Rows; + sourceTree = ""; + }; + DAF457A723DBA2C40018B495 /* Utils */ = { + isa = PBXGroup; + children = ( + DAF4578123D630C70018B495 /* WatchIconView.swift */, + DAF4578823D79AA50018B495 /* DetailTextLabelView.swift */, + DA2E0AA323DC96E9009B0A99 /* ImageWithAction.swift */, + DA2E0B0D23DCC152009B0A99 /* MapView.swift */, + DA0749DF23E0BF510057FA83 /* ColorSelection.swift */, + DA32D1B32C8C98C40018D974 /* IconWithAction.swift */, + DA8B14BB2F3A3CB5007753FD /* WatchTypography.swift */, + ); + path = Utils; sourceTree = ""; }; DFB2621E18830A3600D3244D = { isa = PBXGroup; children = ( + DA448E7E2EF435B400F0893C /* AppIntents */, + DABB4CD22F45EFD100111AF3 /* openHABWatch.xctestplan */, 6557AF8E2C0241C10094D0C8 /* PrivacyInfo.xcprivacy */, DA4D4DB4233F9ACB00B37E37 /* README.md */, DA4D4E0E2340A00200B37E37 /* Changes.md */, DA817E79234BF39B00C91824 /* CHANGELOG.md */, DACC0F7B2E883AC700B62043 /* AGENTS.md */, + 93685A782ADE755C0077A9A6 /* TestPlans */, + DA2DC23021F2736C00830730 /* openHABTestsSwift */, + 933D7F0522E7015000621A03 /* openHABUITests */, + DA0775162346705D0086C685 /* openHABWatch */, + DAD085792AE4782F001D36BE /* openHABWatchSwiftUI Watch AppUITests */, + 6571444F2C1E438700C8A1F3 /* NotificationService */, 2FD4E52B2F670BA500EBB340 /* openHAB */, 2FD4E2F42F66FA2900EBB340 /* TestPlans */, 2FD4E2E02F66F9FE00EBB340 /* openHABTestsSwift */, 2FD4E2CD2F66F9F600EBB340 /* openHABUITests */, 2FD4E29C2F66F9A500EBB340 /* openHABWatch */, - 2FD4E1EF2F66EF5500EBB340 /* openHABIntentsTests */, 2FD4E1EB2F66EF4F00EBB340 /* openHABWatchSwiftUI Watch AppUITests */, 2FD4E1E62F66EF4800EBB340 /* NotificationService */, DA8B15522F3BB74B007753FD /* openHABWatchTests */, - 2F2150892F75C2FC001BA057 /* openHABIntents */, + 2F2150892F75C2FC001BA057 /* Watch */, DFB2622818830A3600D3244D /* Products */, DFB2622918830A3600D3244D /* Frameworks */, 2FD4E1E22F66EF0200EBB340 /* fastlane */, DAFD2FE62E0D96700059A1EB /* OsLogRewriter */, DA0DA9E12E0C9B74000C5D0A /* BuildTools */, DA83C81D2F48AF7600CDACED /* Version.xcconfig */, - 2FD4E92D2F67571C00EBB340 /* Recovered References */, ); sourceTree = ""; }; @@ -454,7 +710,6 @@ DA2DC22F21F2736C00830730 /* openHABTestsSwift.xctest */, 933D7F0422E7015000621A03 /* openHABUITests.xctest */, DA0775152346705D0086C685 /* openHABWatch.app */, - 4D6470D32561F935007B03FC /* openHABIntents.appex */, DAD0856C2AE4782B001D36BE /* openHABWatchSwiftUI Watch AppTests.xctest */, DAD085762AE4782E001D36BE /* openHABWatchSwiftUI Watch AppUITests.xctest */, 6571444E2C1E438700C8A1F3 /* NotificationService.appex */, @@ -479,28 +734,6 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 4D6470D22561F935007B03FC /* openHABIntents */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4D6470DB2561F935007B03FC /* Build configuration list for PBXNativeTarget "openHABIntents" */; - buildPhases = ( - 4D6470CF2561F935007B03FC /* Sources */, - 4D6470D02561F935007B03FC /* Frameworks */, - 4D6470D12561F935007B03FC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = openHABIntents; - packageProductDependencies = ( - 937E4491270B37FE00A98C26 /* Kingfisher */, - 937E44E1270B393C00A98C26 /* OpenHABCore */, - DA7ACD5E2DC3DB130055CFC7 /* SFSafeSymbols */, - ); - productName = openHABIntents; - productReference = 4D6470D32561F935007B03FC /* openHABIntents.appex */; - productType = "com.apple.product-type.app-extension"; - }; 6571444D2C1E438700C8A1F3 /* NotificationService */ = { isa = PBXNativeTarget; buildConfigurationList = 657144582C1E438700C8A1F3 /* Build configuration list for PBXNativeTarget "NotificationService" */; @@ -666,11 +899,9 @@ ); dependencies = ( DA07753A2346705F0086C685 /* PBXTargetDependency */, - 4D6470D92561F935007B03FC /* PBXTargetDependency */, 657144542C1E438700C8A1F3 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - 2FD4E1EF2F66EF5500EBB340 /* openHABIntentsTests */, 2FD4E52B2F670BA500EBB340 /* openHAB */, ); name = openHAB; @@ -703,9 +934,6 @@ LastUpgradeCheck = 2610; ORGANIZATIONNAME = "openHAB e.V."; TargetAttributes = { - 4D6470D22561F935007B03FC = { - CreatedOnToolsVersion = 12.1; - }; 6571444D2C1E438700C8A1F3 = { CreatedOnToolsVersion = 15.4; }; @@ -793,7 +1021,6 @@ DA2DC22E21F2736C00830730 /* openHABTestsSwift */, 933D7F0322E7015000621A03 /* openHABUITests */, DA0775142346705D0086C685 /* openHABWatch */, - 4D6470D22561F935007B03FC /* openHABIntents */, DAD0856B2AE4782A001D36BE /* openHABWatchSwiftUI Watch AppTests */, DAD085752AE4782D001D36BE /* openHABWatchSwiftUI Watch AppUITests */, 6571444D2C1E438700C8A1F3 /* NotificationService */, @@ -803,13 +1030,6 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 4D6470D12561F935007B03FC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6571444C2C1E438700C8A1F3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -864,7 +1084,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2F2150A22F75C434001BA057 /* Intents.intentdefinition in Resources */, 6557AF8F2C0241C10094D0C8 /* PrivacyInfo.xcprivacy in Resources */, DA817E7A234BF39B00C91824 /* CHANGELOG.md in Resources */, DA4D4DB5233F9ACB00B37E37 /* README.md in Resources */, @@ -896,23 +1115,6 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 4D6470CF2561F935007B03FC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2F21508A2F75C2FC001BA057 /* GetItemStateIntentHandler.swift in Sources */, - 2F21508B2F75C2FC001BA057 /* IntentHandler.swift in Sources */, - 2F2150982F75C306001BA057 /* Intents.intentdefinition in Sources */, - 2F21508D2F75C2FC001BA057 /* OpenHABIntentHelper.swift in Sources */, - 2F21508E2F75C2FC001BA057 /* SetColorValueIntentHandler.swift in Sources */, - 2F21508F2F75C2FC001BA057 /* SetContactStateValueIntentHandler.swift in Sources */, - 2F2150902F75C2FC001BA057 /* SetDimmerRollerValueIntentHandler.swift in Sources */, - 2F2150912F75C2FC001BA057 /* SetNumberValueIntentHandler.swift in Sources */, - 2F2150922F75C2FC001BA057 /* SetStringValueIntentHandler.swift in Sources */, - 2F2150932F75C2FC001BA057 /* SetSwitchStateIntentHandler.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6571444A2C1E438700C8A1F3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -966,17 +1168,30 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA448E852EF435B400F0893C /* Home.swift in Sources */, + DA4BF3522F0307A40082479C /* GetItemStateIntent.swift in Sources */, + DA4BF3562F03095D0082479C /* SetColorValueIntent.swift in Sources */, + DA4BF3572F03095D0082479C /* ContactStateIntent.swift in Sources */, + DA4BF3592F03098B0082479C /* SetDimmerRollerValueIntent.swift in Sources */, + DA4BF35B2F0309A60082479C /* SetNumberValueIntent.swift in Sources */, + DA4BF35D2F0309B10082479C /* SetStringValueIntent.swift in Sources */, + DA4BF4032F06F5950082479C /* SwitchAction.swift in Sources */, + DA4BF4052F06F5CA0082479C /* ContactState.swift in Sources */, + DA4BF4072F0800010082479C /* PlayerAction.swift in Sources */, + DA4BF4092F0800020082479C /* SetPlayerValueIntent.swift in Sources */, + DA4BF40B2F0800030082479C /* SetDateTimeValueIntent.swift in Sources */, + DA4BF40D2F0800040082479C /* SetLocationValueIntent.swift in Sources */, + DA4BF4342F0705580082479C /* ItemEntity.swift in Sources */, + DA4BF4362F07056F0082479C /* ItemEntityQuery.swift in Sources */, + DAD5E85C2F02C272003215C0 /* SetSwitchItemIntent.swift in Sources */, + DAD5E85E2F02C3C6003215C0 /* ItemIdentifier.swift in Sources */, + DAF58C7B2EF6E99500483AFD /* SwitchItemEntity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 4D6470D92561F935007B03FC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4D6470D22561F935007B03FC /* openHABIntents */; - targetProxy = 4D6470D82561F935007B03FC /* PBXContainerItemProxy */; - }; 657144542C1E438700C8A1F3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6571444D2C1E438700C8A1F3 /* NotificationService */; @@ -1014,92 +1229,7 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 2F2150972F75C306001BA057 /* Intents.intentdefinition */ = { - isa = PBXVariantGroup; - children = ( - 2F2150962F75C306001BA057 /* Base */, - 2F2150992F75C325001BA057 /* en */, - 2F21509A2F75C327001BA057 /* nl */, - 2F21509B2F75C329001BA057 /* fi */, - 2F21509C2F75C32B001BA057 /* fr */, - 2F21509D2F75C32D001BA057 /* de */, - 2F21509E2F75C32F001BA057 /* it */, - 2F21509F2F75C331001BA057 /* nb */, - 2F2150A02F75C333001BA057 /* ru */, - 2F2150A12F75C335001BA057 /* es */, - ); - name = Intents.intentdefinition; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ - 4D6470DC2561F935007B03FC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = openHABIntents/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4D6470DD2561F935007B03FC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = openHABIntents/openHABIntents.entitlements; - CODE_SIGN_IDENTITY = "Apple Distribution"; - CODE_SIGN_STYLE = Manual; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = openHABIntents/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.openhab.app.openHABIntents; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore org.openhab.app.openHABIntents"; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; 657144562C1E438700C8A1F3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1846,15 +1976,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 4D6470DB2561F935007B03FC /* Build configuration list for PBXNativeTarget "openHABIntents" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4D6470DC2561F935007B03FC /* Debug */, - 4D6470DD2561F935007B03FC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 657144582C1E438700C8A1F3 /* Build configuration list for PBXNativeTarget "NotificationService" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2085,15 +2206,6 @@ package = 937E4483270B379900A98C26 /* XCRemoteSwiftPackageReference "DeviceKit" */; productName = DeviceKit; }; - 937E4491270B37FE00A98C26 /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = 937E4486270B37A600A98C26 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; - 937E44E1270B393C00A98C26 /* OpenHABCore */ = { - isa = XCSwiftPackageProductDependency; - productName = OpenHABCore; - }; 93F8063427AE6C620035A6B0 /* FirebaseCrashlytics */ = { isa = XCSwiftPackageProductDependency; package = 93F8063327AE6C620035A6B0 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; @@ -2124,11 +2236,6 @@ package = DA2C4FD32B4F573300D1C533 /* XCRemoteSwiftPackageReference "SDWebImageSVGCoder" */; productName = SDWebImageSVGCoder; }; - DA7ACD5E2DC3DB130055CFC7 /* SFSafeSymbols */ = { - isa = XCSwiftPackageProductDependency; - package = DA3B75AC2C59729200E219AB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; - productName = SFSafeSymbols; - }; DA9A7EFC2D668D5900824156 /* SFSafeSymbols */ = { isa = XCSwiftPackageProductDependency; package = DA3B75AC2C59729200E219AB /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; diff --git a/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved b/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved index d80759747..c2772ead8 100644 --- a/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/openHAB.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/devicekit/DeviceKit.git", "state" : { - "revision" : "581df61650bc457ec00373a592a84be3e7468eb1", - "version" : "5.7.0" + "revision" : "56b997e8a61707218f9af09f32b2a1d1806fd792", + "version" : "5.8.0" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { - "revision" : "674d9a7ee9858207181a3dd0b42c77298c6fb71b", - "version" : "12.8.0" + "revision" : "85560b48b0ff099ad83fe53d67df3c67fbc2b7a6", + "version" : "12.10.0" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", "state" : { - "revision" : "35b601a60fbbea2de3ea461f604deaaa4d8bbd0c", - "version" : "3.2.0" + "revision" : "a5cd95c80e8efdd02155c6cea1cecf743bb683a5", + "version" : "3.3.0" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "2ffd220823f3716904733162e9ae685545c276d1", - "version" : "12.8.0" + "revision" : "68ba955e540dcff5e0805970ef4b1fd0150be100", + "version" : "12.10.0" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { - "revision" : "fb7f2740b1570d2f7599c6bb9531bf4fad6974b7", - "version" : "5.0.0" + "revision" : "a883ddb9fd464216133a5ab441f1ae8995978573", + "version" : "5.1.0" } }, { diff --git a/openHAB/Supporting Files/Localizable.xcstrings b/openHAB/Supporting Files/Localizable.xcstrings index 8057478e7..dc2515ae1 100644 --- a/openHAB/Supporting Files/Localizable.xcstrings +++ b/openHAB/Supporting Files/Localizable.xcstrings @@ -178,6 +178,18 @@ } } }, + "%@" : { + "comment" : "The display representation of a Home.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + } + } + }, "%@ Settings" : { "comment" : "The title of the settings view. The placeholder is replaced with the user's home name.", "localizations" : { @@ -590,6 +602,65 @@ } } }, + "Action" : { + "comment" : "Parameter title for an action in app intents.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktion" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Action" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acción" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Action" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Azione" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Handling" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actie" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "activate" : { "localizations" : { "de" : { @@ -2822,6 +2893,77 @@ } } }, + "Color Item" : { + "comment" : "Display name for a color item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Farb-Item" + } + } + } + }, + "Command failed: %@" : { + "comment" : "Error message when sending a command to an item fails. Placeholder is the error description.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Befehlsfehler: %@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Command failed: %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "Command failures: %lld" : { "comment" : "A label describing the number of failed commands. The argument is the number of failed commands.", "localizations" : { @@ -3293,6 +3435,77 @@ } } }, + "Contact Item" : { + "comment" : "Display name for a contact item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontakt-Item" + } + } + } + }, + "Contact State" : { + "comment" : "Type display name for the ContactState enum used in app intents.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontakt-Status" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contact State" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "Cool Daylight" : { "comment" : "A description for a color temperature between 6500 and 8000 Kelvin.", "localizations" : { @@ -3821,6 +4034,77 @@ } } }, + "Date and Time" : { + "comment" : "Parameter title for date and time in the Set DateTime Control Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datum und Uhrzeit" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Date and Time" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "DateTime Item" : { + "comment" : "Display name for a DateTimeItemEntity.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DateTime-Item" + } + } + } + }, "Daylight" : { "comment" : "A descriptive label for a color temperature range.", "localizations" : { @@ -4470,6 +4754,18 @@ } } }, + "Dimmer/Roller Item" : { + "comment" : "Display name for a dimmer or roller item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dimmer/Roller-Item" + } + } + } + }, "Disable Idle Timeout" : { "comment" : "A toggle that allows the user to disable the idle timeout feature.", "localizations" : { @@ -4905,6 +5201,7 @@ }, "empty.action" : { "comment" : "empty action", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4964,6 +5261,7 @@ }, "empty.itemname" : { "comment" : "empty item name", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -5023,6 +5321,7 @@ }, "empty.itemorhome" : { "comment" : "Empty Item or empty Home", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -5082,6 +5381,7 @@ }, "empty.value" : { "comment" : "Empty, with value name behind", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -5788,7 +6088,66 @@ } } }, - "Font" : { + "Fast Forward" : { + "comment" : "Display name for the fast forward action in the PlayerAction enum.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schneller Vorlauf" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fast Forward" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Font" : { "comment" : "A label for the font picker in the screen saver settings view.", "localizations" : { "de" : { @@ -5965,6 +6324,75 @@ } } }, + "Get ${itemEntity} State" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "${itemEntity}-Status erhalten" + } + } + } + }, + "Get Item State" : { + "comment" : "Title of the Get Item State app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status eines Items erhalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Get Item State" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obtener estado del ítem" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Récupérer l'état de l'item" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leggi lo stato dell'Item" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Få Item-tilstand" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haal item state op" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "habpanel" : { "extractionState" : "manual", "localizations" : { @@ -6732,84 +7160,84 @@ } } }, - "invalid_connection_configuration" : { - "extractionState" : "manual", + "Invalid value %lld for %@ (0-100)" : { + "comment" : "Error message when a dimmer or roller shutter value is out of range. First placeholder is the invalid value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ungültige Verbindungskonfiguration." + "value" : "Ungültiger Wert %lld für %@ (0-100)" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Invalid connection configuration." + "value" : "Invalid value %lld for %@ (0-100)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración de conexión no válida." + "value" : "Valor no válido %lld para %@ (0-100)" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Virheellinen yhteysasetukset." + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Configuration de connexion invalide." + "value" : "Valeur %lld invalide pour %@ (0-100)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Configurazione della connessione non valida." + "value" : "Valore non valido %lld per %@ (0-100)" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ugyldig tilkoblingskonfigurasjon." + "value" : "Ugyldig verdi %lld for %@ (0-100)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Ongeldige verbindingsconfiguratie." + "value" : "Ongeldige waarde %lld voor %@ (0-100)" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Недопустимая конфигурация подключения." + "state" : "new", + "value" : "" } } } }, - "Items" : { - "comment" : "The title of the view that lists available items.", + "Invalid value: %@ for %@ must be HSB (0-360,0-100,0-100)" : { + "comment" : "Error message when a color value is not valid HSB. First placeholder is the invalid value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Items" + "value" : "Ungültiger Wert: %@ für %@ muss HSB sein (0-360,0-100,0-100)" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Items" + "value" : "Invalid value: %@ for %@ must be HSB (0-360,0-100,0-100)" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ítems" + "value" : "Valor no válido: %@ para %@ debe ser HSB (0-360,0-100,0-100)" } }, "fi" : { @@ -6821,25 +7249,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Éléments" + "value" : "Valeur invalide : %@ pour %@ doit être HSB (0-360,0-100,0-100)" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elementi" + "value" : "Valore non valido: %@ per %@ deve essere HSB (0-360,0-100,0-100)" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ugyldig verdi: %@ for %@ må være HSB (0-360,0-100,0-100)" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Onderdelen" + "value" : "Ongeldige waarde: %@ voor %@ moet HSB zijn (0-360,0-100,0-100)" } }, "ru" : { @@ -6850,143 +7278,143 @@ } } }, - "Label" : { + "invalid_connection_configuration" : { "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Label" + "value" : "Ungültige Verbindungskonfiguration." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Label" + "value" : "Invalid connection configuration." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Etiqueta" + "value" : "Configuración de conexión no válida." } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Virheellinen yhteysasetukset." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Étiquette" + "value" : "Configuration de connexion invalide." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Etichetta" + "value" : "Configurazione della connessione non valida." } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ugyldig tilkoblingskonfigurasjon." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Label" + "value" : "Ongeldige verbindingsconfiguratie." } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Недопустимая конфигурация подключения." } } } }, - "legal" : { - "extractionState" : "manual", + "Item" : { + "comment" : "Parameter title for an openHAB item in app intents.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtliches" + "value" : "Item" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Legal" + "value" : "Item" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Legal" + "value" : "Ítem" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Legal" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Légal" + "value" : "Item" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Legale" + "value" : "Item" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Juridisk" + "value" : "Item" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Juridisch" + "value" : "Item" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Legal" + "state" : "new", + "value" : "" } } } }, - "Legal" : { - "comment" : "A link to the app's legal information.", + "Item '%@' is not in home '%@'" : { + "comment" : "Error message when the selected item does not belong to the selected home. First placeholder is the item name, second is the home name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Rechtliches" + "value" : "Das Item „%@“ befindet sich nicht im Zuhause „%@“" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Legal" + "value" : "Item '%@' is not in home '%@'" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Aviso legal" + "state" : "new", + "value" : "" } }, "fi" : { @@ -6997,14 +7425,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Mentions légales" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Note legali" + "state" : "new", + "value" : "" } }, "nb" : { @@ -7015,8 +7443,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Juridische informatie" + "state" : "new", + "value" : "" } }, "ru" : { @@ -7027,25 +7455,25 @@ } } }, - "Loading Items…" : { - "comment" : "A message displayed while waiting to load items.", + "Items" : { + "comment" : "The title of the view that lists available items.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Items werden geladen …" + "value" : "Items" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Loading Items…" + "value" : "Items" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ítems" } }, "fi" : { @@ -7056,14 +7484,14 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Éléments" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Elementi" } }, "nb" : { @@ -7074,8 +7502,8 @@ }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Onderdelen" } }, "ru" : { @@ -7086,25 +7514,25 @@ } } }, - "Loading sitemap…" : { - "comment" : "A placeholder text indicating that the sitemap is being loaded.", + "Label" : { + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sitemap wird geladen …" + "value" : "Label" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Loading sitemap…" + "value" : "Label" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Etiqueta" } }, "fi" : { @@ -7115,14 +7543,14 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Étiquette" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Etichetta" } }, "nb" : { @@ -7133,8 +7561,8 @@ }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Label" } }, "ru" : { @@ -7145,78 +7573,78 @@ } } }, - "Loading…" : { - "extractionState" : "manual", + "Latitude" : { + "comment" : "Parameter title for latitude in the Set Location Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wird geladen …" + "value" : "Breitengrad" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Loading…" + "value" : "Latitude" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Cargando…" + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Ladataan …" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Chargement…" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Caricamento …" + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Laster …" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Laden…" + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Загрузка …" + "state" : "new", + "value" : "" } } } }, - "Local server" : { - "comment" : "Title of the section in the connection settings view that pertains to the local OpenHAB server.", + "Latitude must be between -90 and 90" : { + "comment" : "Error message when a latitude value is out of the valid range.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lokaler Server" + "value" : "Breitengrad muss zwischen -90 and 90 sein" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Local server" + "value" : "Latitude must be between -90 and 90" } }, "es" : { @@ -7263,142 +7691,84 @@ } } }, - "local_url" : { + "legal" : { + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Lokale URL" + "value" : "Rechtliches" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Local URL" + "value" : "Legal" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "URL local" + "value" : "Legal" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Local URL" + "value" : "Legal" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "URL locale" + "value" : "Légal" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "URL locale" + "value" : "Legale" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Lokal URL" + "value" : "Juridisk" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Lokale URL" + "value" : "Juridisch" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Локальный URL-адрес" - } - } - } - }, - "Location" : { - "comment" : "Name of a marker displayed on a map view.", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standort" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Location" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ubicación" - } - }, - "fi" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Emplacement" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Posizione" - } - }, - "nb" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Locatie" - } - }, - "ru" : { - "stringUnit" : { - "state" : "new", - "value" : "" + "value" : "Legal" } } } }, - "Logs" : { - "comment" : "A link in the debug settings that navigates to the logs viewer.", + "Legal" : { + "comment" : "A link to the app's legal information.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Protokolle" + "value" : "Rechtliches" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Logs" + "value" : "Legal" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Registros" + "value" : "Aviso legal" } }, "fi" : { @@ -7410,13 +7780,13 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Historiques" + "value" : "Mentions légales" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Log" + "value" : "Note legali" } }, "nb" : { @@ -7428,7 +7798,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Logbestanden" + "value" : "Juridische informatie" } }, "ru" : { @@ -7439,19 +7809,19 @@ } } }, - "Lowers by %@" : { - "comment" : "A hint that describes how much the value can be decreased by.", + "Loading Items…" : { + "comment" : "A message displayed while waiting to load items.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Um %@ senken" + "value" : "Items werden geladen …" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Lowers by %@" + "value" : "Loading Items…" } }, "es" : { @@ -7498,25 +7868,25 @@ } } }, - "Main" : { - "comment" : "The header text for the \"Main\" section in the drawer view.", + "Loading sitemap…" : { + "comment" : "A placeholder text indicating that the sitemap is being loaded.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Main" + "value" : "Sitemap wird geladen …" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Main" + "value" : "Loading sitemap…" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Principal" + "state" : "new", + "value" : "" } }, "fi" : { @@ -7527,14 +7897,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Principal" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Principale" + "state" : "new", + "value" : "" } }, "nb" : { @@ -7545,8 +7915,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Hoofd" + "state" : "new", + "value" : "" } }, "ru" : { @@ -7557,77 +7927,78 @@ } } }, - "mainui_settings" : { + "Loading…" : { + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Main UI-Einstellungen" + "value" : "Wird geladen …" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Main UI Settings" + "value" : "Loading…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes de la interfaz de usuario principal" + "value" : "Cargando…" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Main UI Settings" + "value" : "Ladataan …" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres Main UI" + "value" : "Chargement…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni Main UI" + "value" : "Caricamento …" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillinger for Main UI" + "value" : "Laster …" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Main UI Instellingen" + "value" : "Laden…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Main UI Settings" + "value" : "Загрузка …" } } } }, - "Manage Homes" : { - "comment" : "The title of a view that allows users to manage their homes.", + "Local server" : { + "comment" : "Title of the section in the connection settings view that pertains to the local OpenHAB server.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zuhause verwalten" + "value" : "Lokaler Server" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Manage Homes" + "value" : "Local server" } }, "es" : { @@ -7674,83 +8045,83 @@ } } }, - "message_not_decoded" : { + "local_url" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nachricht konnte nicht dekodiert werden" + "value" : "Lokale URL" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Message could not be decoded" + "value" : "Local URL" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No se ha podido decodificar el mensaje" + "value" : "URL local" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Message could not be decoded" + "value" : "Local URL" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le message n'a pas pu être décodé" + "value" : "URL locale" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il messaggio non può essere decodificato" + "value" : "URL locale" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Melding kunne ikke dekodes" + "value" : "Lokal URL" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bericht kon niet gedecodeerd worden" + "value" : "Lokale URL" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Message could not be decoded" + "value" : "Локальный URL-адрес" } } } }, - "Movement Interval: %lld s" : { - "comment" : "A stepper that allows the user to set the interval in seconds between when the screen saver will activate and when it will deactivate after a user is detected moving.", + "Location" : { + "comment" : "Name of a marker displayed on a map view.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bewegungsintervall: %lld s" + "value" : "Standort" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Movement Interval: %lld s" + "value" : "Location" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ubicación" } }, "fi" : { @@ -7761,14 +8132,14 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Emplacement" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Posizione" } }, "nb" : { @@ -7779,8 +8150,8 @@ }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Locatie" } }, "ru" : { @@ -7791,25 +8162,37 @@ } } }, - "Name" : { - "extractionState" : "manual", + "Location Item" : { + "comment" : "Display name for a location item type.", + "isCommentAutoGenerated" : true, "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Name" + "value" : "Standort-Item" + } + } + } + }, + "Logs" : { + "comment" : "A link in the debug settings that navigates to the logs viewer.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protokolle" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Name" + "value" : "Logs" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nombre" + "value" : "Registros" } }, "fi" : { @@ -7821,13 +8204,13 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nom" + "value" : "Historiques" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nome" + "value" : "Log" } }, "nb" : { @@ -7839,7 +8222,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Naam" + "value" : "Logbestanden" } }, "ru" : { @@ -7850,19 +8233,19 @@ } } }, - "Name for new home" : { - "comment" : "A placeholder label for a text field in an alert used to enter the name of a new home.", + "Longitude" : { + "comment" : "Parameter title for longitude in the Set Location Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Name für neues Zuhause" + "value" : "Längengrad" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Name for new home" + "value" : "Longitude" } }, "es" : { @@ -7909,77 +8292,78 @@ } } }, - "network_not_available" : { + "Longitude must be between -180 and 180" : { + "comment" : "Error message when a longitude value is out of the valid range.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Netzwerk ist nicht verfügbar." + "value" : "Längengrad muss zwischen -180 and 180 sein" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Network is not available." + "value" : "Longitude must be between -180 and 180" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Red no disponible." + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Network is not available." + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Aucune connexion réseau disponible." + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Rete non disponibile." + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Nettverk er ikke tilgjengelig." + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Netwerk is niet beschikbaar." + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Network is not available." + "state" : "new", + "value" : "" } } } }, - "New name" : { - "comment" : "A label for a text field where the user can enter a new name for a home.", + "Lowers by %@" : { + "comment" : "A hint that describes how much the value can be decreased by.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Name" + "value" : "Um %@ senken" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "New name" + "value" : "Lowers by %@" } }, "es" : { @@ -8026,25 +8410,25 @@ } } }, - "No accepted server certificates" : { - "comment" : "A message displayed when there are no accepted server certificates.", + "Main" : { + "comment" : "The header text for the \"Main\" section in the drawer view.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine akzeptierten Server-Zertifikate" + "value" : "Main" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No accepted server certificates" + "value" : "Main" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Principal" } }, "fi" : { @@ -8055,14 +8439,14 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Principal" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Principale" } }, "nb" : { @@ -8073,8 +8457,8 @@ }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Hoofd" } }, "ru" : { @@ -8085,77 +8469,77 @@ } } }, - "No Image URL" : { + "mainui_settings" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Bild-URL" + "value" : "Main UI-Einstellungen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No Image URL" + "value" : "Main UI Settings" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ajustes de la interfaz de usuario principal" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Main UI Settings" } }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Paramètres Main UI" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Impostazioni Main UI" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Innstillinger for Main UI" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Main UI Instellingen" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Main UI Settings" } } } }, - "No sitemaps available" : { - "comment" : "A message indicating that there are no sitemaps available to choose from in the \"Sitemap For Apple Watch\" picker.", + "Manage Homes" : { + "comment" : "The title of a view that allows users to manage their homes.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Sitemaps verfügbar" + "value" : "Zuhause verwalten" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No sitemaps available" + "value" : "Manage Homes" } }, "es" : { @@ -8202,78 +8586,77 @@ } } }, - "No Video URL" : { - "comment" : "A message displayed when a video URL is not provided for a video row.", + "message_not_decoded" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Video-URL" + "value" : "Nachricht konnte nicht dekodiert werden" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No Video URL" + "value" : "Message could not be decoded" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "No se ha podido decodificar el mensaje" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Message could not be decoded" } }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Le message n'a pas pu être décodé" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Il messaggio non può essere decodificato" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Melding kunne ikke dekodes" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Bericht kon niet gedecodeerd worden" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Message could not be decoded" } } } }, - "No widgets available." : { - "comment" : "A message displayed when a user has no widgets configured.", + "Movement Interval: %lld s" : { + "comment" : "A stepper that allows the user to set the interval in seconds between when the screen saver will activate and when it will deactivate after a user is detected moving.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Widgets verfügbar." + "value" : "Bewegungsintervall: %lld s" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No widgets available." + "value" : "Movement Interval: %lld s" } }, "es" : { @@ -8320,78 +8703,78 @@ } } }, - "no_active_connection" : { + "Name" : { "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine aktive Verbindung vorhanden. Bitte Einstellungen prüfen" + "value" : "Name" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No active connection available. Please check your settings." + "value" : "Name" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No active connection available. Please check your settings." + "value" : "Nombre" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "No active connection available. Please check your settings." + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "No active connection available. Please check your settings." + "value" : "Nom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "No active connection available. Please check your settings." + "value" : "Nome" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "No active connection available. Please check your settings." + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "No active connection available. Please check your settings." + "value" : "Naam" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "No active connection available. Please check your settings." + "state" : "new", + "value" : "" } } } }, - "no_connection_will_reconnect" : { - "comment" : "Title of a popup message displayed when the OpenHAB client is not connected to the server and will automatically try to reconnect.", + "Name for new home" : { + "comment" : "A placeholder label for a text field in an alert used to enter the name of a new home.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Verbindung, erneuter Verbindungsversuch" + "value" : "Name für neues Zuhause" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "no_connection_will_reconnect" + "value" : "Name for new home" } }, "es" : { @@ -8438,200 +8821,2462 @@ } } }, - "no_servers_found" : { + "network_not_available" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Netzwerk ist nicht verfügbar." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Network is not available." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Red no disponible." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Network is not available." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucun serveur trouvé" + "value" : "Aucune connexion réseau disponible." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Rete non disponibile." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Nettverk er ikke tilgjengelig." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Netwerk is niet beschikbaar." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "No servers found" + "value" : "Network is not available." } } } }, - "notification" : { + "New name" : { + "comment" : "A label for a text field where the user can enter a new name for a home.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benachrichtigung" + "value" : "Neuer Name" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Notification" + "value" : "New name" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Notificación" + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Notification" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Notification" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Notifica" + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Varsel" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Notificatie" + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Notification" + "state" : "new", + "value" : "" } } } }, - "notifications" : { - "extractionState" : "manual", + "Next" : { + "comment" : "Display name for the next action in the PlayerAction enum.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benachrichtigungen" + "value" : "Nächstes" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Notifications" + "value" : "Next" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Notificaciones" + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Notifications" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "No accepted server certificates" : { + "comment" : "A message displayed when there are no accepted server certificates.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine akzeptierten Server-Zertifikate" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No accepted server certificates" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "No Image URL" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Bild-URL" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Image URL" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "No sitemaps available" : { + "comment" : "A message indicating that there are no sitemaps available to choose from in the \"Sitemap For Apple Watch\" picker.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Sitemaps verfügbar" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No sitemaps available" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "No Video URL" : { + "comment" : "A message displayed when a video URL is not provided for a video row.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Video-URL" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Video URL" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "No widgets available." : { + "comment" : "A message displayed when a user has no widgets configured.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Widgets verfügbar." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No widgets available." + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "no_active_connection" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine aktive Verbindung vorhanden. Bitte Einstellungen prüfen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "No active connection available. Please check your settings." + } + } + } + }, + "no_connection_will_reconnect" : { + "comment" : "Title of a popup message displayed when the OpenHAB client is not connected to the server and will automatically try to reconnect.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Verbindung, erneuter Verbindungsversuch" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "no_connection_will_reconnect" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "no_servers_found" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun serveur trouvé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "No servers found" + } + } + } + }, + "notification" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigung" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notification" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificación" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notification" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notification" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifica" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Varsel" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificatie" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notification" + } + } + } + }, + "notifications" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificaciones" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifiche" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Varsler" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificaties" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + } + } + }, + "Notifications" : { + "comment" : "The title of the notifications view.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificaciones" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", "value" : "Notifications" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Notifiche" + "value" : "Notifiche" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificaties" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Number Item" : { + "comment" : "Display name for a number item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Numerisches-Item" + } + } + } + }, + "off" : { + "comment" : "Label for the \"off\" state of a contact.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "desactivada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "désactivé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "non attivo" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "uit" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Off" : { + "comment" : "The text \"Off\" displayed in the accessibility value of the toggle.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desactivado" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désactivé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "No" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uit" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Offline" : { + "comment" : "A label indicating that the device is currently offline.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Offline" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Offline" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin conexión" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Déconnecté" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non in linea" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Offline" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "oh_secret" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB-Secret" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Secret" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "código secreto de openHAB" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Secret" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Phrase secrète openHAB" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Secret" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Hemmelighet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB geheim" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Secret" + } + } + } + }, + "oh_uuid" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB-UUID" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB UUID" + } + } + } + }, + "oh_version" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB-Version" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Version" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Versión de openHAB" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Version" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Version" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Versione openHAB" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB versjon" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Versie" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Version" + } + } + } + }, + "OK" : { + "comment" : "The text for an OK button.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aceptar" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "on" : { + "comment" : "\"on\" in capitalized form.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "an" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "on" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "activada" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "activé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "attivo" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "aan" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "On" : { + "comment" : "A label indicating that a switch is on.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "An" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "On" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Attivo" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aan" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Once" : { + "comment" : "Button to allow the app to connect to the server only once, even if the certificate is invalid.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einmal" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Once" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "open" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "offen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "open" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "abierto" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "open" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ouvert" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "aperto" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "åpen" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "open" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "open" + } + } + } + }, + "openHAB Cloud Service" : { + "comment" : "A toggle that allows the user to enable or disable notifications from the openHAB Cloud Service.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Cloud-Dienst" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Cloud Service" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "openhab_connection" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Verbindung" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Connection" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "conexión openHAB" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Connection" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connexion openHAB" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connessione openHAB" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Tilkobling" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "openHAB Verbinding" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "подключение к OpenHAB" + } + } + } + }, + "password" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contraseña" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mot de passe" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passord" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + } + } + }, + "Password" : { + "comment" : "A label displayed above a text field for entering a password.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contraseña" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mot de passe" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Pause" : { + "comment" : "Display name for the pause action in the PlayerAction enum.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Pick a Color" : { + "comment" : "A title for the color picker interface.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Farbe auswählen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pick a Color" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Play" : { + "comment" : "Display name for the play action in the PlayerAction enum.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abspielen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Play" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Player Action" : { + "comment" : "Type display name for the PlayerAction enum used in app intents.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Player-Aktion" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Player Action" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Player Item" : { + "comment" : "Display name for a player item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Player-Item" + } + } + } + }, + "PNG" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "PNG" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Preferences" : { + "comment" : "Label for the preferences tab in the watch app.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferences" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferencias" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Préférences" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferenze" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorkeuren" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Preview state" : { + "comment" : "The accessibility label for the picker that allows the user to select and preview a different state of an interactive state token.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorschauzustand" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preview state" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Previous" : { + "comment" : "Display name for the previous action in the PlayerAction enum.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorheriges" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previous" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "privacy_policy" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datenschutzerklärung" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Política de privacidad" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Politique de Confidentialité" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Politica sulla privacy" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personvernregler" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacybeleid" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacy Policy" + } + } + } + }, + "Queued commands: %lld" : { + "comment" : "A label indicating that there are one or more commands in the queue. The placeholder inside the label is replaced with the actual number of queued commands.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wartende Befehle: %lld" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Queued commands: %lld" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Raises by %@" : { + "comment" : "A hint that appears when a user hovers over the \"Increase\" button in the setpoint row. The argument is the step size for the current setpoint.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Um %@ erhöhen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raises by %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "real_time_sliders" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Echtzeitschieberegler" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Real-time Sliders" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deslizadores en tiempo real" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Real-time Sliders" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Curseurs en temps réel" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cursori In Tempo Reale" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Varsler" + "value" : "Sanntids Skyveknapper" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Notificaties" + "value" : "Realtime schuifregelaars" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Notifications" + "value" : "Real-time Sliders" } } } }, - "Notifications" : { - "comment" : "The title of the notifications view.", + "Real-time Sliders" : { + "comment" : "A toggle that enables or disables the real-time slider feature.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benachrichtigungen" + "value" : "Echtzeitschieberegler" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Notifications" + "value" : "Real-time Sliders" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Notificaciones" + "state" : "new", + "value" : "" } }, "fi" : { @@ -8642,14 +11287,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Notifications" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Notifiche" + "state" : "new", + "value" : "" } }, "nb" : { @@ -8660,8 +11305,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Notificaties" + "state" : "new", + "value" : "" } }, "ru" : { @@ -8672,25 +11317,25 @@ } } }, - "off" : { - "comment" : "Label for the \"off\" state of a contact.", + "Relative Date: %lld %%" : { + "comment" : "A label describing the relative size of the date text compared to the clock text.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "aus" + "value" : "Relatives Datum: %lld %%" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "off" + "value" : "Relative Date: %lld %%" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "desactivada" + "state" : "new", + "value" : "" } }, "fi" : { @@ -8701,14 +11346,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "désactivé" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "non attivo" + "state" : "new", + "value" : "" } }, "nb" : { @@ -8719,8 +11364,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "uit" + "state" : "new", + "value" : "" } }, "ru" : { @@ -8731,25 +11376,25 @@ } } }, - "Off" : { - "comment" : "The text \"Off\" displayed in the accessibility value of the toggle.", + "Remote server" : { + "comment" : "Header text for the remote server section in the connection settings view.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Aus" + "value" : "Fernzugriff-Server" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Off" + "value" : "Remote server" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Desactivado" + "state" : "new", + "value" : "" } }, "fi" : { @@ -8760,14 +11405,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Désactivé" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "No" + "state" : "new", + "value" : "" } }, "nb" : { @@ -8778,8 +11423,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Uit" + "state" : "new", + "value" : "" } }, "ru" : { @@ -8790,261 +11435,261 @@ } } }, - "Offline" : { - "comment" : "A label indicating that the device is currently offline.", + "remote_url" : { + "comment" : "Space constraint string which has only about half the screen width on Apple Watch. Best to try and keep it no longer than the English string.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Offline" + "value" : "Fern-URL" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Offline" + "value" : "Remote URL" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin conexión" + "value" : "URL remota" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Remote URL" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Déconnecté" + "value" : "URL distante" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non in linea" + "value" : "URL remoto" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Ekstern URL" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Offline" + "value" : "Externe URL" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Удаленный URL-адрес" } } } }, - "oh_secret" : { + "remote_url_not_configured" : { "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB-Secret" + "value" : "URL für Fernzugriff ist nicht konfiguriert." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Secret" + "value" : "Remote URL is not configured." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "código secreto de openHAB" + "value" : "La URL remota no está configurada." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Secret" + "value" : "Remote URL is not configured." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Phrase secrète openHAB" + "value" : "L'URL distante n'est pas configurée." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Secret" + "value" : "L'URL remoto non è configurato." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Hemmelighet" + "value" : "Ekstern URL er ikke konfigurert." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB geheim" + "value" : "Externe URL is niet geconfigureerd." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Secret" + "value" : "Remote URL is not configured." } } } }, - "oh_uuid" : { - "extractionState" : "manual", + "Rename" : { + "comment" : "A button that renames a home.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB-UUID" + "value" : "Umbenennen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB UUID" + "value" : "Rename" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB UUID" + "value" : "Renombrar" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB UUID" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB UUID" + "value" : "Renommer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB UUID" + "value" : "Rinomina" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB UUID" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB UUID" + "value" : "Wijzig naam" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB UUID" + "state" : "new", + "value" : "" } } } }, - "oh_version" : { - "extractionState" : "manual", + "Restore Brightness: %lld %%" : { + "comment" : "A label under the slider that shows the current brightness level and allows the user to adjust it. The value shown is the brightness level as a percentage.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB-Version" + "value" : "Helligkeit wiederherstellen: %lld %%" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Version" + "value" : "Restore Brightness: %lld %%" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Versión de openHAB" + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Version" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Version" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Versione openHAB" + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB versjon" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Versie" + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Version" + "state" : "new", + "value" : "" } } } }, - "OK" : { - "comment" : "The text for an OK button.", + "Restore Previous Brightness on Wake" : { + "comment" : "A toggle that allows the user to restore the brightness level to its previous value when the device wakes up.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Vorherige Helligkeit beim Aufwecken wiederherstellen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Restore Previous Brightness on Wake" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Aceptar" + "state" : "new", + "value" : "" } }, "fi" : { @@ -9055,14 +11700,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "OK" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "OK" + "state" : "new", + "value" : "" } }, "nb" : { @@ -9073,8 +11718,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "OK" + "state" : "new", + "value" : "" } }, "ru" : { @@ -9085,25 +11730,25 @@ } } }, - "on" : { - "comment" : "\"on\" in capitalized form.", + "Retrieve the current state of an item" : { + "comment" : "Description of the Get Item State app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "an" + "value" : "Aktuellen Status eines Items abrufen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "on" + "value" : "Retrieve the current state of an item" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "activada" + "value" : "Recuperar el estado actual de un elemento" } }, "fi" : { @@ -9115,25 +11760,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "activé" + "value" : "Récupérer l'état actuel d'un item" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "attivo" + "value" : "Recupera lo stato attuale di un Item" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Hent nåværende tilstand for et Item" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "aan" + "value" : "Haal de huidige status van een item op" } }, "ru" : { @@ -9144,78 +11789,78 @@ } } }, - "On" : { - "comment" : "A label indicating that a switch is on.", + "retry" : { + "comment" : "retry connection", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "An" + "value" : "Erneut versuchen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "On" + "value" : "Retry" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Reintentar" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Retry" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Activé" + "value" : "Réessayer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Attivo" + "value" : "Riprova" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Prøv på nytt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aan" + "value" : "Opnieuw" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Retry" } } } }, - "Once" : { - "comment" : "Button to allow the app to connect to the server only once, even if the certificate is invalid.", + "Rewind" : { + "comment" : "Display name for the rewind action in the PlayerAction enum.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einmal" + "value" : "Zurückspulen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Once" + "value" : "Rewind" } }, "es" : { @@ -9262,78 +11907,78 @@ } } }, - "open" : { + "running_demo_mode" : { "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "offen" + "value" : "Läuft im Demomodus. Überprüfe die Einstellungen, um den Demomodus zu deaktivieren." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "open" + "value" : "Running in demo mode. Check settings to disable demo mode." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "abierto" + "value" : "Funccionando en modo demostración. Comprueba la configuración para desactivarlo." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "open" + "value" : "Running in demo mode. Check settings to disable demo mode." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "ouvert" + "value" : "Exécution en mode démo. Vérifiez les paramètres pour désactiver le mode démo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "aperto" + "value" : "Esecuzione in modalità demo. Controlla le impostazioni per disabilitare la modalità demo." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "åpen" + "value" : "Kjører i demomodus. Sjekk innstillingene for å deaktivere demomodus." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "open" + "value" : "Uitvoeren in demo-modus. Controleer instellingen om demomodus uit te schakelen." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "open" + "value" : "Running in demo mode. Check settings to disable demo mode." } } } }, - "openHAB Cloud Service" : { - "comment" : "A toggle that allows the user to enable or disable notifications from the openHAB Cloud Service.", + "Saturation: %.2f" : { + "comment" : "A label displaying the saturation value in the color picker view.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Cloud-Dienst" + "value" : "Sättigung: %.2f" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Cloud Service" + "value" : "Saturation: %.2f" } }, "es" : { @@ -9380,142 +12025,202 @@ } } }, - "openhab_connection" : { - "extractionState" : "manual", + "Save" : { + "comment" : "The text for a button that saves current settings.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Verbindung" + "value" : "Sichern" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Connection" + "value" : "Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "conexión openHAB" + "value" : "Guardar" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Connection" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Connexion openHAB" + "value" : "Enregistrer" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connessione openHAB" + "value" : "Salva" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "openHAB Tilkobling" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "openHAB Verbinding" + "value" : "Bewaar" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "подключение к OpenHAB" + "state" : "new", + "value" : "" } } } }, - "password" : { + "Screen Saver" : { + "comment" : "The title of the screen.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort" + "value" : "Bildschirmschoner" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Screen Saver" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseña" + "value" : "Salvapantallas" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Password" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mot de passe" + "value" : "Économiseur d’écran" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Salvaschermo" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Passord" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord" + "value" : "Schermbeveiliging" } }, "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Screen Saver Settings" : { + "comment" : "A link to the settings related to the screen saver.", + "localizations" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Bildschirmschoner-Einstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Saver Settings" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" } } } }, - "Password" : { - "comment" : "A label displayed above a text field for entering a password.", + "Search" : { + "comment" : "A text field for searching through a list of items.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort" + "value" : "Suchen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Password" + "value" : "Search" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Contraseña" + "value" : "Buscar" } }, "fi" : { @@ -9527,13 +12232,13 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mot de passe" + "value" : "Rechercher" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Cerca" } }, "nb" : { @@ -9545,7 +12250,7 @@ "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wachtwoord" + "value" : "Zoek" } }, "ru" : { @@ -9556,19 +12261,19 @@ } } }, - "Pick a Color" : { - "comment" : "A title for the color picker interface.", + "Search for an item" : { + "comment" : "Dialog text prompting the user to search for an openHAB item in app intents.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Farbe auswählen" + "value" : "Nach Item suchen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Pick a Color" + "value" : "Search for an item" } }, "es" : { @@ -9615,84 +12320,87 @@ } } }, - "PNG" : { - "extractionState" : "manual", + "search_items" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Suche openHAB Items" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Search openHAB items" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Buscar ítems en openHAB" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Search openHAB items" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Rechercher des items openHAB" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Cerca Item openHAB" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Søk i openHAB Items" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "PNG" + "value" : "Zoek openHAB items" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Search openHAB items" } } } }, - "Preferences" : { - "comment" : "Label for the preferences tab in the watch app.", + "Select a home for '%@'" : { + "comment" : "A message to be displayed in a dialog box when the user needs to select a home. The argument is the name of the item that needs a home.", + "isCommentAutoGenerated" : true + }, + "Select color" : { + "comment" : "A button that, when pressed, opens a view allowing the user to select a color.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" + "value" : "Farbe wählen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Preferences" + "value" : "Select color" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Preferencias" + "state" : "new", + "value" : "" } }, "fi" : { @@ -9703,14 +12411,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Préférences" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Preferenze" + "state" : "new", + "value" : "" } }, "nb" : { @@ -9721,8 +12429,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Voorkeuren" + "state" : "new", + "value" : "" } }, "ru" : { @@ -9733,136 +12441,146 @@ } } }, - "Preview state" : { - "comment" : "The accessibility label for the picker that allows the user to select and preview a different state of an interactive state token.", + "select_sitemap" : { + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorschauzustand" + "value" : "Sitemap auswählen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Preview state" + "value" : "Select Sitemap" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Seleccionar mapa web" } }, "fi" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Select Sitemap" } }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Sélectionner le Sitemap" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Seleziona Sitemap" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Velg Sitemap" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Selecteer Sitemap" } }, "ru" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Выбрать Sitemap" } } } }, - "privacy_policy" : { + "Send ${action} to ${itemEntity}" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Datenschutzerklärung" + "value" : "${action} an ${itemEntity} senden" + } + } + } + }, + "Send a player command such as play, pause, next, or previous" : { + "comment" : "Description of the Set Player Control Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Player-Befehl senden, z. B. Abspielen, Pause, Weiter oder Zurück" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Privacy Policy" + "value" : "Send a player command such as play, pause, next, or previous" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Política de privacidad" + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Privacy Policy" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Politique de Confidentialité" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Politica sulla privacy" + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Personvernregler" + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Privacybeleid" + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Privacy Policy" + "state" : "new", + "value" : "" } } } }, - "Queued commands: %lld" : { - "comment" : "A label indicating that there are one or more commands in the queue. The placeholder inside the label is replaced with the actual number of queued commands.", + "Sending commands: %lld" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wartende Befehle: %lld" + "value" : "Befehle werden gesendet: %lld" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Queued commands: %lld" + "value" : "Sending commands: %lld" } }, "es" : { @@ -9909,19 +12627,19 @@ } } }, - "Raises by %@" : { - "comment" : "A hint that appears when a user hovers over the \"Increase\" button in the setpoint row. The argument is the step size for the current setpoint.", + "Sent location %lf, %lf to %@" : { + "comment" : "Dialog shown after setting a location value. First two placeholders are latitude and longitude, third is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Um %@ erhöhen" + "value" : "%lf, %lf wurde an Standort %@ gesendet" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Raises by %@" + "value" : "Sent location %lf, %lf to %@" } }, "es" : { @@ -9968,78 +12686,78 @@ } } }, - "real_time_sliders" : { - "extractionState" : "manual", + "Sent the color value of %@ to %@" : { + "comment" : "Dialog shown after setting a color value. First placeholder is the color value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Echtzeitschieberegler" + "value" : "Der Farbwert %@ wurde an %@ gesendet" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Real-time Sliders" + "value" : "Sent the color value of %@ to %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Deslizadores en tiempo real" + "value" : "Enviado el valor de color de %@ a %@" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Real-time Sliders" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Curseurs en temps réel" + "value" : "Envoyé la valeur de couleur de %@ à %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cursori In Tempo Reale" + "value" : "Inviato il valore di colore di %@ a %@" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Sanntids Skyveknapper" + "value" : "Sendte fargeverdien %@ til %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Realtime schuifregelaars" + "value" : "De kleurwaarde van %@ is verzonden naar %@" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Real-time Sliders" + "state" : "new", + "value" : "" } } } }, - "Real-time Sliders" : { - "comment" : "A toggle that enables or disables the real-time slider feature.", + "Sent the date %@ to %@" : { + "comment" : "Dialog shown after setting a date/time value. First placeholder is the date string, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Echtzeitschieberegler" + "value" : "Datum %@ auf %@ setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Real-time Sliders" + "value" : "Sent the date %@ to %@" } }, "es" : { @@ -10086,25 +12804,25 @@ } } }, - "Relative Date: %lld %%" : { - "comment" : "A label describing the relative size of the date text compared to the clock text.", + "Sent the number %lf to %@" : { + "comment" : "Dialog shown after setting a number control value. First placeholder is the decimal value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Relatives Datum: %lld %%" + "value" : "Die Zahl %lf wurde an %@ gesendet" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Relative Date: %lld %%" + "value" : "Sent the number %lf to %@" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Definir el número %lf a %@" } }, "fi" : { @@ -10115,26 +12833,26 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Envoyé le numéro %lf à %@" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Inviato il numero %lf a %@" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Send den numeriske verdien %lf til %@" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "De waarde %lf is verzonden naar %@" } }, "ru" : { @@ -10145,25 +12863,25 @@ } } }, - "Remote server" : { - "comment" : "Header text for the remote server section in the connection settings view.", + "Sent the string %@ to %@" : { + "comment" : "Dialog shown after setting a string control value. First placeholder is the string value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fernzugriff-Server" + "value" : "Der String %@ wurde an %@ gesendet" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Remote server" + "value" : "Sent the string %@ to %@" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Enviada la cadena %@ a %@" } }, "fi" : { @@ -10174,26 +12892,26 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Envoi de la chaîne %@ à %@" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Inviata la stringa %@ a %@" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Send strengverdien %@ til %@" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "De waarde %@ is verzonden naar %@" } }, "ru" : { @@ -10204,143 +12922,173 @@ } } }, - "remote_url" : { - "comment" : "Space constraint string which has only about half the screen width on Apple Watch. Best to try and keep it no longer than the English string.", + "Sent the value of %lld to %@" : { + "comment" : "Dialog shown after setting a dimmer or roller shutter value. First placeholder is the integer value, second is the item name.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fern-URL" + "value" : "Der Wert %lld wurde an %@ gesendet" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Remote URL" + "value" : "Sent the value of %lld to %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "URL remota" + "value" : "Enviado el valor de %lld a %@" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Remote URL" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "URL distante" + "value" : "Envoyé la valeur %lld à %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "URL remoto" + "value" : "Inviato il valore %lld a %@" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ekstern URL" + "value" : "Sendte verdien %lld til %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Externe URL" + "value" : "De waarde %lld is verzonden naar %@" } }, "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Set ${itemEntity} to ${latitude}, ${longitude}" : { + "localizations" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Удаленный URL-адрес" + "value" : "Standort ${itemEntity} auf ${latitude}, ${longitude} setzen" } } } }, - "remote_url_not_configured" : { - "extractionState" : "manual", + "Set ${itemEntity} to ${value}" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "URL für Fernzugriff ist nicht konfiguriert." + "value" : "${itemEntity} auf ${value} setzen" + } + } + } + }, + "Set ${itemEntity} to ${value} (HSB)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "${itemEntity} auf ${value} (HSB) setzen" + } + } + } + }, + "Set Color Control Value" : { + "comment" : "Title of the Set Color Control Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Farbwert festlegen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Remote URL is not configured." + "value" : "Set Color Control Value" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La URL remota no está configurada." + "value" : "Define el valor de control de color" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Remote URL is not configured." + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'URL distante n'est pas configurée." + "value" : "Définir la valeur de contrôle de couleur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'URL remoto non è configurato." + "value" : "Imposta Valore Controllo Colore" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ekstern URL er ikke konfigurert." + "value" : "Angi Fargekontrollverdi" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Externe URL is niet geconfigureerd." + "value" : "Kleurwaarde instellen" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Remote URL is not configured." + "state" : "new", + "value" : "" } } } }, - "Rename" : { - "comment" : "A button that renames a home.", + "Set Contact State Value" : { + "comment" : "Title of the Set Contact State Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Umbenennen" + "value" : "Wert eines Kontakt=Status festlegen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Rename" + "value" : "Set Contact State Value" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Renombrar" + "value" : "Establecer el valor del estado del pulsador" } }, "fi" : { @@ -10352,25 +13100,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Renommer" + "value" : "Définir la valeur de l'état du contacteur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rinomina" + "value" : "Imposta lo Stato del Contatto" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Sett Tilstand for Bryter" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Wijzig naam" + "value" : "Stel contact status waarde in" } }, "ru" : { @@ -10381,19 +13129,19 @@ } } }, - "Restore Brightness: %lld %%" : { - "comment" : "A label under the slider that shows the current brightness level and allows the user to adjust it. The value shown is the brightness level as a percentage.", + "Set DateTime Control Value" : { + "comment" : "Title of the Set DateTime Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Helligkeit wiederherstellen: %lld %%" + "value" : "Wert eines DateTime-Kontrollelements setzen " } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Restore Brightness: %lld %%" + "value" : "Set DateTime Control Value" } }, "es" : { @@ -10440,19 +13188,78 @@ } } }, - "Restore Previous Brightness on Wake" : { - "comment" : "A toggle that allows the user to restore the brightness level to its previous value when the device wakes up.", + "Set Dimmer or Roller Shutter Value" : { + "comment" : "Title of the Set Dimmer or Roller Shutter Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vorherige Helligkeit beim Aufwecken wiederherstellen" + "value" : "Wert eines Dimmers oder Rollladens festlegen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Restore Previous Brightness on Wake" + "value" : "Set Dimmer or Roller Shutter Value" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definir el valor del regulador o persiana" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Régler la valeur du Dimmer ou du Volet Roulant" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imposta il valore di Dimmer o Tapparella" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Angi Verdi for Dimmer eller Rullegardin" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel Dimmer of Rolluik waarde in" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "Set Location Control Value" : { + "comment" : "Title of the Set Location Control Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standort-Kontrollwert setzen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set Location Control Value" } }, "es" : { @@ -10499,143 +13306,143 @@ } } }, - "retry" : { - "comment" : "retry connection", + "Set Number Control Value" : { + "comment" : "Title of the Set Number Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erneut versuchen" + "value" : "Wert eines numerischen Kontrollelements setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Retry" + "value" : "Set Number Control Value" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Reintentar" + "value" : "Establecer valor de control de número" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Retry" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réessayer" + "value" : "Définir la valeur de contrôle du numéro" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Riprova" + "value" : "Imposta Valore del Numero di Controllo" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Prøv på nytt" + "value" : "Sett Verdi for Numerisk Kontroll" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw" + "value" : "Zet Nummer Control Waarde" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Retry" + "state" : "new", + "value" : "" } } } }, - "running_demo_mode" : { - "extractionState" : "manual", + "Set Player Control Value" : { + "comment" : "Title of the Set Player Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Läuft im Demomodus. Überprüfe die Einstellungen, um den Demomodus zu deaktivieren." + "value" : "Player-Kontrollelement setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Running in demo mode. Check settings to disable demo mode." + "value" : "Set Player Control Value" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Funccionando en modo demostración. Comprueba la configuración para desactivarlo." + "state" : "new", + "value" : "" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Running in demo mode. Check settings to disable demo mode." + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "Exécution en mode démo. Vérifiez les paramètres pour désactiver le mode démo." + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Esecuzione in modalità demo. Controlla le impostazioni per disabilitare la modalità demo." + "state" : "new", + "value" : "" } }, "nb" : { "stringUnit" : { - "state" : "translated", - "value" : "Kjører i demomodus. Sjekk innstillingene for å deaktivere demomodus." + "state" : "new", + "value" : "" } }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Uitvoeren in demo-modus. Controleer instellingen om demomodus uit te schakelen." + "state" : "new", + "value" : "" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Running in demo mode. Check settings to disable demo mode." + "state" : "new", + "value" : "" } } } }, - "Saturation: %.2f" : { - "comment" : "A label displaying the saturation value in the color picker view.", + "Set String Control Value" : { + "comment" : "Title of the Set String Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sättigung: %.2f" + "value" : "String-Kontrollwert setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Saturation: %.2f" + "value" : "Set String Control Value" } }, "es" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Establecer el valor de control de cadena" } }, "fi" : { @@ -10646,26 +13453,26 @@ }, "fr" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Définir la valeur de contrôle de chaîne" } }, "it" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Imposta Valore della Stringa di Controllo" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Sett Strengkontroll-Verdi" } }, "nl" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Zet String Control Waarde in" } }, "ru" : { @@ -10676,25 +13483,25 @@ } } }, - "Save" : { - "comment" : "The text for a button that saves current settings.", + "Set Switch State" : { + "comment" : "Title of the Set Switch State app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sichern" + "value" : "Schalterzustand setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Set Switch State" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Definir el estado del interruptor" } }, "fi" : { @@ -10706,25 +13513,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer" + "value" : "Définir l'état du Switch" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salva" + "value" : "Imposta stato Interruttore" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Angi Brytertilstand" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Bewaar" + "value" : "Stel schakelstatus in" } }, "ru" : { @@ -10735,25 +13542,25 @@ } } }, - "Screen Saver" : { - "comment" : "The title of the screen.", + "Set the color of a color control item" : { + "comment" : "Description of the Set Color Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bildschirmschoner" + "value" : "Farbe eines Kontrollelements festlegen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Screen Saver" + "value" : "Set the color of a color control item" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salvapantallas" + "value" : "Definir el color de un ítem de control de color" } }, "fi" : { @@ -10765,25 +13572,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Économiseur d’écran" + "value" : "Définir la couleur d'un élément de contrôle de couleur" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Salvaschermo" + "value" : "Imposta il colore di un Item di controllo colore" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Angi fargen til et fargekontroll-Item" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Schermbeveiliging" + "value" : "Stel de kleur van een kleurbesturingsitem in" } }, "ru" : { @@ -10794,19 +13601,19 @@ } } }, - "Screen Saver Settings" : { - "comment" : "A link to the settings related to the screen saver.", + "Set the date and time of a DateTime control item" : { + "comment" : "Description of the Set DateTime Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bildschirmschoner-Einstellungen" + "value" : "Datum und Uhrzeit eines DateTime-Kontrollelements setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Screen Saver Settings" + "value" : "Set the date and time of a DateTime control item" } }, "es" : { @@ -10853,25 +13660,25 @@ } } }, - "Search" : { - "comment" : "A text field for searching through a list of items.", + "Set the decimal value of a number control item" : { + "comment" : "Description of the Set Number Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Suchen" + "value" : "Dezimalwert eines numerischen Kontrollelements setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Search" + "value" : "Set the decimal value of a number control item" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar" + "value" : "Establecer el valor decimal de un ítem de control numérico" } }, "fi" : { @@ -10883,25 +13690,25 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher" + "value" : "Définir la valeur décimale d'un item de contrôle de nombre" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca" + "value" : "Imposta il valore decimale di un Item di controllo numerico" } }, "nb" : { "stringUnit" : { - "state" : "new", - "value" : "" + "state" : "translated", + "value" : "Sett desimalverdien til en numerisk kontroll-Item" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoek" + "value" : "Stel de decimale waarde van een nummer control item in" } }, "ru" : { @@ -10912,77 +13719,78 @@ } } }, - "search_items" : { + "Set the integer value of a dimmer or roller shutter" : { + "comment" : "Description of the Set Dimmer or Roller Shutter Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Suche openHAB Items" + "value" : "Festlegen des Wertes eines Dimmers oder Rollladens" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Search openHAB items" + "value" : "Set the integer value of a dimmer or roller shutter" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar ítems en openHAB" + "value" : "Define el valor entero de un regulador o persiana" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Search openHAB items" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher des items openHAB" + "value" : "Définir la valeur entière d'un dimmer ou d'un volet roulant" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca Item openHAB" + "value" : "Imposta il valore intero di un dimmer o una tapparella" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Søk i openHAB Items" + "value" : "Sett heltallsverdien til en dimmer eller rullegardin" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoek openHAB items" + "value" : "Zet de integerwaarde van een dimmer of rolluik" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Search openHAB items" + "state" : "new", + "value" : "" } } } }, - "Select color" : { - "comment" : "A button that, when pressed, opens a view allowing the user to select a color.", + "Set the latitude and longitude of a location control item" : { + "comment" : "Description of the Set Location Control Value app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Farbe wählen" + "value" : "Breitengrad und Längengrad eines Standort-Kontrollelements setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Select color" + "value" : "Set the latitude and longitude of a location control item" } }, "es" : { @@ -11029,77 +13837,88 @@ } } }, - "select_sitemap" : { - "extractionState" : "manual", + "Set the state of ${itemEntity} to ${state}" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sitemap auswählen" + "value" : "Den Status von${itemEntity} auf ${state} setzen" + } + } + } + }, + "Set the state of a contact open or closed" : { + "comment" : "Description of the Set Contact State Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status eines Kontakts auf offen oder geschlossen setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Select Sitemap" + "value" : "Set the state of a contact open or closed" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar mapa web" + "value" : "Establecer el estado de un interruptor encendido o apagado" } }, "fi" : { "stringUnit" : { - "state" : "translated", - "value" : "Select Sitemap" + "state" : "new", + "value" : "" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Sélectionner le Sitemap" + "value" : "Définir l'état d'un contacteur ouvert ou fermé" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona Sitemap" + "value" : "Imposta lo stato di un contatto aperto o chiuso" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Velg Sitemap" + "value" : "Sett tilstanden til en bryter til åpen eller lukket" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer Sitemap" + "value" : "Zet de status van een contact open of gesloten" } }, "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Выбрать Sitemap" + "state" : "new", + "value" : "" } } } }, - "Sending commands: %lld" : { + "Set the state of a switch on or off, or toggle its state" : { + "comment" : "Description of the Set Switch State app intent.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Befehle werden gesendet: %lld" + "value" : "Switch-Status auf An, Aus oder Umschalten setzen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Sending commands: %lld" + "value" : "Set the state of a switch on or off, or toggle its state" } }, "es" : { @@ -11146,6 +13965,65 @@ } } }, + "Set the string of a string control item" : { + "comment" : "Description of the Set String Control Value app intent.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeichenkette eines String-Kontrollelements setzen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set the string of a string control item" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Establecer la cadena de un ítem controlador de cadena" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Définir la chaîne d'un item de contrôle de chaîne" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Imposta il valore di una item di controllo di tipo stringa" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Angi teksten til et teksterkontroll-Item" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De tekenreeks van een string controle item instellen" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "Set value" : { "comment" : "A button that sets the value of a text input.", "localizations" : { @@ -12433,30 +15311,101 @@ "ru" : { "stringUnit" : { "state" : "translated", - "value" : "SSL Certificate Warning" + "value" : "SSL Certificate Warning" + } + } + } + }, + "State" : { + "comment" : "A label for a picker that lets the user select a state.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "State" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Provincia" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "État" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stato" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "String Item" : { + "comment" : "Display name for a string item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "String-Item" } } } }, - "State" : { - "comment" : "A label for a picker that lets the user select a state.", + "SVG" : { + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zustand" + "value" : "SVG" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "State" + "value" : "SVG" } }, "es" : { "stringUnit" : { - "state" : "translated", - "value" : "Provincia" + "state" : "new", + "value" : "" } }, "fi" : { @@ -12467,14 +15416,14 @@ }, "fr" : { "stringUnit" : { - "state" : "translated", - "value" : "État" + "state" : "new", + "value" : "" } }, "it" : { "stringUnit" : { - "state" : "translated", - "value" : "Stato" + "state" : "new", + "value" : "" } }, "nb" : { @@ -12485,8 +15434,8 @@ }, "nl" : { "stringUnit" : { - "state" : "translated", - "value" : "Status" + "state" : "new", + "value" : "" } }, "ru" : { @@ -12497,19 +15446,19 @@ } } }, - "SVG" : { - "extractionState" : "manual", + "Switch Action" : { + "comment" : "Type display name for the SwitchAction enum used in app intents.", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "SVG" + "value" : "Switch-Aktion" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "SVG" + "value" : "Switch Action" } }, "es" : { @@ -12556,6 +15505,18 @@ } } }, + "Switch Item" : { + "comment" : "Display name for a switch item type.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Switch-Item" + } + } + } + }, "sync_prefs" : { "extractionState" : "manual", "localizations" : { @@ -12851,6 +15812,124 @@ } } }, + "The state of %@ is %@" : { + "comment" : "Dialog shown when retrieving an item state. First placeholder is the item name, second is its state.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Status von %@ ist %@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The state of %@ is %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El estado de %@ es %@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'état de %@ est %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lo stato di %@ è %@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilstanden til %@ er %@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De staat van %@ is %@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, + "The state of %@ was set to %@" : { + "comment" : "Dialog shown after setting a contact state. First placeholder is the item name, second is the new state.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Status von %@ wurde auf %@ gesetzt" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The state of %@ was set to %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El estado de %@ se ha establecido en %@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'état de %@ a été réglé sur %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lo stato di %@ è stato impostato su %@" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilstanden til %@ ble satt til %@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "De status van %@ is ingesteld op %@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "The URL is invalid. Please check the format (e.g., http://192.168.2.1:8080)." : { "comment" : "Error message displayed when the URL is invalid.", "localizations" : { @@ -13028,6 +16107,77 @@ } } }, + "toggle" : { + "comment" : "Action to toggle a switch between on and off states", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "umschalten" + } + } + } + }, + "Toggle" : { + "comment" : "Display name for the toggle action in the SwitchAction enum.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umschalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toggle" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "unable_to_add_certificate" : { "extractionState" : "manual", "localizations" : { @@ -13264,8 +16414,13 @@ } } }, + "Unknown home" : { + "comment" : "Error message displayed when a home is selected but the home ID is not found in the list of stored homes.", + "isCommentAutoGenerated" : true + }, "unknownHome" : { "comment" : "unknown home", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -13325,11 +16480,12 @@ }, "unknownState" : { "comment" : "unknown item state", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Unbekannter Zustand" + "value" : "Unbekannter Status" } }, "en" : { @@ -13676,6 +16832,65 @@ } } }, + "Value" : { + "comment" : "Parameter title for a value in app intents.", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wert" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Value" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Valor" + } + }, + "fi" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Valeur" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Valore" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verdi" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Waarde" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "version" : { "localizations" : { "de" : { diff --git a/openHABIntents/Base.lproj/Intents.intentdefinition b/openHABIntents/Base.lproj/Intents.intentdefinition deleted file mode 100644 index 7a7156bc3..000000000 --- a/openHABIntents/Base.lproj/Intents.intentdefinition +++ /dev/null @@ -1,2060 +0,0 @@ - - - - - INEnums - - INIntentDefinitionModelVersion - 1.2 - INIntentDefinitionNamespace - aK4nIm - INIntentDefinitionSystemVersion - 25D2128 - INIntentDefinitionToolsBuildVersion - 17C529 - INIntentDefinitionToolsVersion - 26.3 - INIntents - - - INIntentCategory - request - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Retrieve the current state of an item - INIntentDescriptionID - GD4RTw - INIntentIneligibleForSuggestions - - INIntentKeyParameter - item - INIntentLastParameterTag - 5 - INIntentManagedParameterCombinations - - item,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Get ${item} State - INIntentParameterCombinationTitleID - m9yAKr - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - GetItemState - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - cBrxnz - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - WSKSgZ - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Item Name - INIntentParameterPromptDialogFormatStringID - NehcMF - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - k6pj4o - INIntentParameterDisplayPriority - 2 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - P9kblb - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - rsrXB9 - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - 7BE642 - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - 4WCzFG - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - iKSmqU - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 5 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - The state of ${item} is ${state} - INIntentResponseCodeFormatStringID - 6hUiXZ - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - hflKDd - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseLastParameterTag - 2 - INIntentResponseOutput - state - INIntentResponseParameters - - - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 1 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - State - INIntentResponseParameterDisplayNameID - m5DFXq - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - state - INIntentResponseParameterTag - 2 - INIntentResponseParameterType - String - - - - INIntentTitle - Get Item State - INIntentTitleID - 5Uu4bK - INIntentType - Custom - INIntentVerb - Request - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the state of a switch on or off - INIntentDescriptionID - oOjAU0 - INIntentKeyParameter - item - INIntentLastParameterTag - 4 - INIntentManagedParameterCombinations - - item,action,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Send ${action} to ${item} - INIntentParameterCombinationTitleID - oUolm5 - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetSwitchState - INIntentParameterCombinations - - item,action,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Send ${action} to ${item} - INIntentParameterCombinationTitleID - jyXazc - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - M8mX5O - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - h0kR74 - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Switch name - INIntentParameterPromptDialogFormatStringID - lqlrGx - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Action - INIntentParameterDisplayNameID - i8fP5e - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - L1xpr9 - - INIntentParameterName - action - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Action - INIntentParameterPromptDialogFormatStringID - 1GaCDV - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 2 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - X9cN9h - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - v3hsWV - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - YUoeAL - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - h91G9C - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - Jn2aNw - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 4 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - Sent the action of ${action} to switch ${item} - INIntentResponseCodeFormatStringID - Fb0pUB - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - 80K2Dt - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - Action invalid: ${action} for ${item} - INIntentResponseCodeFormatStringID - iCUEet - INIntentResponseCodeName - failureInvalidAction - - - INIntentResponseLastParameterTag - 4 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - KXqi2s - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 3 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Action - INIntentResponseParameterDisplayNameID - TJ1p5Y - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - action - INIntentResponseParameterTag - 4 - INIntentResponseParameterType - String - - - - INIntentTitle - Set Switch State - INIntentTitleID - XMNs7r - INIntentType - Custom - INIntentVerb - Do - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the integer value of a dimmer or roller shutter - INIntentDescriptionID - pg4Bec - INIntentKeyParameter - item - INIntentLastParameterTag - 5 - INIntentManagedParameterCombinations - - item,value,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - MGYSky - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetDimmerRollerValue - INIntentParameterCombinations - - item,value,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - Ch9Akw - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - 1USOx9 - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - 99zKKs - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Dimmer/Roller Name - INIntentParameterPromptDialogFormatStringID - 68Y2Ya - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Value - INIntentParameterDisplayNameID - mAKsWc - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataMaximumValue - 100 - INIntentParameterMetadataType - Field - - INIntentParameterName - value - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterTag - 3 - INIntentParameterType - Integer - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - ZuBsOg - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - rVXwR2 - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - BaNYuS - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - QATq1i - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - exrmKW - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 5 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - Sent the value of ${value} to ${item} - INIntentResponseCodeFormatStringID - wm0fqx - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - N5YsnB - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - Invalid empty value for ${item} - INIntentResponseCodeFormatStringID - iRjffC - INIntentResponseCodeName - failureEmptyValue - - - INIntentResponseCodeFormatString - Invalid value ${value} for ${item} (0-100) - INIntentResponseCodeFormatStringID - V0QsN4 - INIntentResponseCodeName - failureInvalidValue - - - INIntentResponseLastParameterTag - 3 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - Nuhhxp - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 1 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Value - INIntentResponseParameterDisplayNameID - tuvmDw - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - value - INIntentResponseParameterTag - 3 - INIntentResponseParameterType - Integer - - - - INIntentTitle - Set Dimmer or Roller Shutter Value - INIntentTitleID - UZYwuH - INIntentType - Custom - INIntentVerb - Do - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the decimal value of a number control item - INIntentDescriptionID - pptfkD - INIntentKeyParameter - item - INIntentLastParameterTag - 6 - INIntentManagedParameterCombinations - - item,value,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - mVz5mJ - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetNumberValue - INIntentParameterCombinations - - item,value,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - qYPBPC - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - tclrCp - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - 99zKKs - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Dimmer/Roller Name - INIntentParameterPromptDialogFormatStringID - 68Y2Ya - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Value - INIntentParameterDisplayNameID - dtRamI - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataMaximumValue - 100 - INIntentParameterMetadataType - Field - - INIntentParameterName - value - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterTag - 4 - INIntentParameterType - Decimal - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - zLlY3g - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - IS2nO0 - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - lLFYtM - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - jbUbYw - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - VEApie - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 6 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - Sent the number ${value} to ${item} - INIntentResponseCodeFormatStringID - CZfT5a - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - Oazi8d - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - Invalid empty value for ${item} - INIntentResponseCodeFormatStringID - 8OBnvT - INIntentResponseCodeName - failureEmptyValue - - - INIntentResponseLastParameterTag - 4 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - MCp4x6 - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 1 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Value - INIntentResponseParameterDisplayNameID - E291x3 - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - value - INIntentResponseParameterTag - 4 - INIntentResponseParameterType - Decimal - - - - INIntentTitle - Set Number Control Value - INIntentTitleID - o35DYr - INIntentType - Custom - INIntentVerb - Do - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the string of a string control item - INIntentDescriptionID - 5B4e9l - INIntentKeyParameter - item - INIntentLastParameterTag - 7 - INIntentManagedParameterCombinations - - item,value,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - SMYYBh - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetStringValue - INIntentParameterCombinations - - item,value,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} - INIntentParameterCombinationTitleID - 1cBKdm - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - mpxNDZ - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - 99zKKs - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Dimmer/Roller Name - INIntentParameterPromptDialogFormatStringID - 68Y2Ya - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Value - INIntentParameterDisplayNameID - 3evvza - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - None - INIntentParameterMetadataDefaultValueID - m4EqGd - INIntentParameterMetadataDisableAutocorrect - - INIntentParameterMetadataDisableSmartDashes - - INIntentParameterMetadataDisableSmartQuotes - - - INIntentParameterName - value - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterTag - 5 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - 71MWtI - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - YFIIBn - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - MIqlld - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - 3LMXV8 - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - gobcZi - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 7 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - Sent the string ${value} to ${item} - INIntentResponseCodeFormatStringID - JUznKl - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - NjP70V - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - Invalid empty value for ${item} - INIntentResponseCodeFormatStringID - 67qFTP - INIntentResponseCodeName - failureEmptyValue - - - INIntentResponseLastParameterTag - 5 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - skEQxZ - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 1 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Value - INIntentResponseParameterDisplayNameID - HGbUGw - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - value - INIntentResponseParameterTag - 5 - INIntentResponseParameterType - String - - - - INIntentTitle - Set String Control Value - INIntentTitleID - gBn7U6 - INIntentType - Custom - INIntentVerb - Do - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the color of a color control item - INIntentDescriptionID - DxO2wk - INIntentKeyParameter - item - INIntentLastParameterTag - 7 - INIntentManagedParameterCombinations - - item,value,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} (HSB) - INIntentParameterCombinationTitleID - VmFkwo - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetColorValue - INIntentParameterCombinations - - item,value,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set ${item} to ${value} (HSB) - INIntentParameterCombinationTitleID - E07xjp - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - B2I9b5 - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - 99zKKs - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Dimmer/Roller Name - INIntentParameterPromptDialogFormatStringID - 68Y2Ya - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Value - INIntentParameterDisplayNameID - llL4sP - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - None - INIntentParameterMetadataDefaultValue - 240,100,100 - INIntentParameterMetadataDefaultValueID - uGsyyj - INIntentParameterMetadataDisableAutocorrect - - INIntentParameterMetadataDisableSmartDashes - - INIntentParameterMetadataDisableSmartQuotes - - - INIntentParameterName - value - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterTag - 5 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - iBrALm - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - eFNxrT - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - jP4VSa - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - mHbznY - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - tkWfxz - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 7 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - Sent the color value of ${value} to ${item} - INIntentResponseCodeFormatStringID - TK8Rrn - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - ptGHON - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - Invalid value: ${value} for ${item} must be HSB (0-360,0-100,0-100) - INIntentResponseCodeFormatStringID - raIAR6 - INIntentResponseCodeName - failureInvalidValue - - - INIntentResponseLastParameterTag - 5 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - VTeXc1 - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 1 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Value - INIntentResponseParameterDisplayNameID - 8dhboX - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - value - INIntentResponseParameterTag - 5 - INIntentResponseParameterType - String - - - - INIntentTitle - Set Color Control Value - INIntentTitleID - 1Kf0qe - INIntentType - Custom - INIntentVerb - Do - - - INIntentCategory - generic - INIntentClassPrefix - OpenHAB - INIntentConfigurable - - INIntentDescription - Set the state of a contact open or closed - INIntentDescriptionID - eFb7vM - INIntentKeyParameter - item - INIntentLastParameterTag - 4 - INIntentManagedParameterCombinations - - item,state,home - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set the state of ${item} to ${state} - INIntentParameterCombinationTitleID - ttiBzN - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - SetContactStateValue - INIntentParameterCombinations - - item,state,home - - INIntentParameterCombinationIsLinked - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Set the state of ${item} to ${state} - INIntentParameterCombinationTitleID - WsMKz2 - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Item - INIntentParameterDisplayNameID - gQzHiG - INIntentParameterDisplayPriority - 1 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - h0kR74 - - INIntentParameterName - item - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Switch name - INIntentParameterPromptDialogFormatStringID - lqlrGx - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 1 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - State - INIntentParameterDisplayNameID - n7cmvU - INIntentParameterDisplayPriority - 2 - INIntentParameterMetadata - - INIntentParameterMetadataCapitalization - Sentences - INIntentParameterMetadataDefaultValueID - L1xpr9 - - INIntentParameterName - state - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Action - INIntentParameterPromptDialogFormatStringID - 1GaCDV - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterTag - 2 - INIntentParameterType - String - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - home - INIntentParameterDisplayNameID - 7GA2x6 - INIntentParameterDisplayPriority - 3 - INIntentParameterName - home - INIntentParameterObjectType - Home - INIntentParameterObjectTypeNamespace - aK4nIm - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Home name - INIntentParameterPromptDialogFormatStringID - Ncumus - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} configured homes with an item named '${item}'’. - INIntentParameterPromptDialogFormatStringID - H2V4UV - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - For which home do you want to get the value? - INIntentParameterPromptDialogFormatStringID - 9Pu17E - INIntentParameterPromptDialogType - DisambiguationSelection - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${home}’? - INIntentParameterPromptDialogFormatStringID - 0hYnWM - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 4 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - The state of ${item} was set to ${state} - INIntentResponseCodeFormatStringID - H26PEQ - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseCodeFormatString - Sorry can't find ${item} - INIntentResponseCodeFormatStringID - 2q0TdY - INIntentResponseCodeName - failureInvalidItem - - - INIntentResponseCodeFormatString - State invalid: ${state} for ${item} - INIntentResponseCodeFormatStringID - GI0faP - INIntentResponseCodeName - failureInvalidAction - - - INIntentResponseLastParameterTag - 4 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Item - INIntentResponseParameterDisplayNameID - k0eXWc - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - item - INIntentResponseParameterTag - 3 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - State - INIntentResponseParameterDisplayNameID - Nqxuw0 - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - state - INIntentResponseParameterTag - 4 - INIntentResponseParameterType - String - - - - INIntentTitle - Set Contact State Value - INIntentTitleID - nzw2UT - INIntentType - Custom - INIntentVerb - Do - - - INTypes - - - INTypeClassPrefix - OpenHAB - INTypeDisplayName - Home - INTypeDisplayNameID - zV2TH2 - INTypeLastPropertyTag - 100 - INTypeName - Home - INTypeProperties - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 1 - INTypePropertyName - identifier - INTypePropertyTag - 1 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 2 - INTypePropertyName - displayString - INTypePropertyTag - 2 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 3 - INTypePropertyName - pronunciationHint - INTypePropertyTag - 3 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 4 - INTypePropertyName - alternativeSpeakableMatches - INTypePropertySupportsMultipleValues - - INTypePropertyTag - 4 - INTypePropertyType - SpeakableString - - - - - - diff --git a/openHABIntents/GetItemStateIntentHandler.swift b/openHABIntents/GetItemStateIntentHandler.swift deleted file mode 100644 index ddd0b70c1..000000000 --- a/openHABIntents/GetItemStateIntentHandler.swift +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class GetItemStateIntentHandler: NSObject, OpenHABGetItemStateIntentHandling { - func resolveHome(for intent: OpenHABGetItemStateIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABGetItemStateIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideItemOptionsCollection(for intent: OpenHABGetItemStateIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm) - } - - func provideItemOptionsCollection(for intent: OpenHABGetItemStateIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home) - } - - func confirm(intent: OpenHABGetItemStateIntent) async -> OpenHABGetItemStateIntentResponse { - OpenHABGetItemStateIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABGetItemStateIntent) async -> OpenHABGetItemStateIntentResponse { - Logger.intentHandling.info("GetItemStateIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - let item = await OpenHABItemCache.instance.getItemUncached(name: itemName, home: homeId) - - guard let item else { - return .failureInvalidItem(itemName) - } - - return .success( - item: itemName, - state: item.state ?? String(localized: "unknownState", comment: "unknown item state") - ) - } -} diff --git a/openHABIntents/Info.plist b/openHABIntents/Info.plist deleted file mode 100644 index 0755d22c1..000000000 --- a/openHABIntents/Info.plist +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - openHABIntents - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSExtension - - NSExtensionAttributes - - IntentsRestrictedWhileLocked - - IntentsRestrictedWhileProtectedDataUnavailable - - IntentsSupported - - OpenHABGetItemStateIntent - OpenHABSetColorValueIntent - OpenHABSetContactStateValueIntent - OpenHABSetDimmerRollerValueIntent - OpenHABSetNumberValueIntent - OpenHABSetStringValueIntent - OpenHABSetSwitchStateIntent - - - NSExtensionPointIdentifier - com.apple.intents-service - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).IntentHandler - - - diff --git a/openHABIntents/IntentHandler.swift b/openHABIntents/IntentHandler.swift deleted file mode 100644 index 339e42cff..000000000 --- a/openHABIntents/IntentHandler.swift +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Intents -import OpenHABCore - -class IntentHandler: INExtension { - override init() { - super.init() - - Task { @MainActor in - // Ensure Preferences initializes on the MainActor to avoid crashes - _ = Preferences.shared - await OpenHABItemCache.instance.forceCacheReload() - } - } - - override func handler(for intent: INIntent) -> Any { - switch intent { - case is OpenHABGetItemStateIntent: GetItemStateIntentHandler() - case is OpenHABSetSwitchStateIntent: SetSwitchStateIntentHandler() - case is OpenHABSetNumberValueIntent: SetNumberValueIntentHandler() - case is OpenHABSetStringValueIntent: SetStringValueIntentHandler() - case is OpenHABSetColorValueIntent: SetColorValueIntentHandler() - case is OpenHABSetContactStateValueIntent: SetContactStateValueIntentHandler() - default: SetDimmerRollerValueIntentHandler() - } - } -} diff --git a/openHABIntents/OpenHABIntentHelper.swift b/openHABIntents/OpenHABIntentHelper.swift deleted file mode 100644 index 61096da55..000000000 --- a/openHABIntents/OpenHABIntentHelper.swift +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore - -@MainActor -public enum OpenHABIntentHelper { - static func resolveHome(home: OpenHABHome?, item: String?) async -> OpenHABHomeResolutionResult { - if let home, let homeId = home.uuid { - // TODO: fuzzy matching / account for potential renaming? - // TODO: accept potential mismatches if item name is unique - let homePrefs = Preferences.shared.storedHomes.first { $0.key == homeId } - if homePrefs != nil { - return .success(with: home) - } else { - return .unsupported() // given home is not found in preferences - } - } else if let item { - // try to find the home by home-specific item selection - let allItems = await OpenHABItemCache.instance.getAllCachedItems() - let homeIdsWithMatchingItems = allItems.map(\.key).filter { uuid in - allItems[uuid]?.filtered(by: item).isEmpty != true - } - let potentialHomes = homeIdsWithMatchingItems - .compactMap { Preferences.shared.storedHomes[$0] } - .map { OpenHABHome(homeId: $0.id, homeName: $0.homeName) } - if potentialHomes.count == 1 { - return .success(with: potentialHomes[0]) - } else { - return .disambiguation(with: potentialHomes) - } - } else { - return .needsValue() - } - } - - static func getHomeOptions() -> INObjectCollection { - INObjectCollection(items: Preferences.shared.storedHomes.map { OpenHABHome(homeId: $0.value.id, homeName: $0.value.homeName) }) - } - - static func getItemOptions(home: OpenHABHome?, searchTerm: String? = nil, itemTypes: [OpenHABItem.ItemType]? = nil) async -> INObjectCollection { - let allItems = await getAllItems(home: home) - let items = allItems.filtered(by: searchTerm, for: itemTypes) - return INObjectCollection(items: items.map(\.name).map { $0 as NSString }) - } - - private static func getAllItems(home: OpenHABHome?) async -> [OpenHABItem] { - if let home, let homeId = home.uuid { - await OpenHABItemCache.instance.getCachedItems(home: homeId) ?? [] - } else { - await OpenHABItemCache.instance.getAllCachedItems().flatMap(\.value) - } - } -} - -extension OpenHABHome: @unchecked Sendable { - var uuid: UUID? { - UUID(uuidString: identifier ?? "") - } - - convenience init(homeId: UUID, homeName: String) { - self.init(identifier: homeId.uuidString, display: homeName) - } -} - -extension OpenHABHomeResolutionResult: @unchecked Sendable {} - -extension INObjectCollection: @unchecked @retroactive Sendable {} diff --git a/openHABIntents/SetColorValueIntentHandler.swift b/openHABIntents/SetColorValueIntentHandler.swift deleted file mode 100644 index e61fe08de..000000000 --- a/openHABIntents/SetColorValueIntentHandler.swift +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class SetColorValueIntentHandler: NSObject, OpenHABSetColorValueIntentHandling { - func resolveHome(for intent: OpenHABSetColorValueIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetColorValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideItemOptionsCollection(for intent: OpenHABSetColorValueIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.color]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetColorValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.color]) - } - - func confirm(intent: OpenHABSetColorValueIntent) async -> OpenHABSetColorValueIntentResponse { - OpenHABSetColorValueIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetColorValueIntent) async -> OpenHABSetColorValueIntentResponse { - Logger.intentHandling.info("SetColorValueIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard var value = intent.value else { - return .failureInvalidValue( - String(localized: "empty.value", defaultValue: "Empty", comment: "Empty, with value name behind"), - item: itemName - ) - } - - let hsb = value.split(separator: ",") - guard hsb.count == 3, - let hue = Int(hsb[0]), (0 ... 360).contains(hue), - let sat = Int(hsb[1]), (0 ... 100).contains(sat), - let val = Int(hsb[2]), (0 ... 100).contains(val) else { - return .failureInvalidValue(value, item: itemName) - } - - value = "\(hue),\(sat),\(val)" - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: value) - - return .success(value: value, item: itemName) - } -} diff --git a/openHABIntents/SetContactStateValueIntentHandler.swift b/openHABIntents/SetContactStateValueIntentHandler.swift deleted file mode 100644 index 429158a08..000000000 --- a/openHABIntents/SetContactStateValueIntentHandler.swift +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class SetContactStateValueIntentHandler: NSObject, OpenHABSetContactStateValueIntentHandling { - private static let onLabel = String(localized: "on", comment: "").capitalized - private static let offLabel = String(localized: "off", comment: "").capitalized - - private static let localizedActions = [onLabel, offLabel] - private static let actionMap: [String: String] = [ - onLabel: "ON", - offLabel: "OFF" - ] - - func resolveHome(for intent: OpenHABSetContactStateValueIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetContactStateValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideStateOptionsCollection(for intent: OpenHABSetContactStateValueIntent) async throws -> INObjectCollection { - INObjectCollection(items: Self.localizedActions as [NSString]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetContactStateValueIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.contact]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetContactStateValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.contact]) - } - - func confirm(intent: OpenHABSetContactStateValueIntent) async -> OpenHABSetContactStateValueIntentResponse { - OpenHABSetContactStateValueIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetContactStateValueIntent) async -> OpenHABSetContactStateValueIntentResponse { - Logger.intentHandling.info("SetContactStateValueIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard let state = intent.state else { - return .failureInvalidAction( - state: String(localized: "empty.value", defaultValue: "Empty", comment: "Empty, with value name behind"), - item: itemName - ) - } - - guard let realState = Self.actionMap[state] else { - return .failureInvalidAction(state: state, item: itemName) - } - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: realState) - - return .success(item: itemName, state: state) - } -} diff --git a/openHABIntents/SetDimmerRollerValueIntentHandler.swift b/openHABIntents/SetDimmerRollerValueIntentHandler.swift deleted file mode 100644 index 38c125e07..000000000 --- a/openHABIntents/SetDimmerRollerValueIntentHandler.swift +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class SetDimmerRollerValueIntentHandler: NSObject, OpenHABSetDimmerRollerValueIntentHandling { - func resolveHome(for intent: OpenHABSetDimmerRollerValueIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetDimmerRollerValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideItemOptionsCollection(for intent: OpenHABSetDimmerRollerValueIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.dimmer, .rollershutter]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetDimmerRollerValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.dimmer, .rollershutter]) - } - - func confirm(intent: OpenHABSetDimmerRollerValueIntent) async -> OpenHABSetDimmerRollerValueIntentResponse { - OpenHABSetDimmerRollerValueIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetDimmerRollerValueIntent) async -> OpenHABSetDimmerRollerValueIntentResponse { - Logger.intentHandling.info("SetDimmerRollerValueIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard let value = intent.value else { - return .failureEmptyValue(item: itemName) - } - - let number = Int(truncating: value) - - guard (0 ... 100).contains(number) else { - return .failureInvalidValue(value, item: itemName) - } - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: "\(number)") - - return .success(value: NSNumber(value: number), item: itemName) - } -} diff --git a/openHABIntents/SetNumberValueIntentHandler.swift b/openHABIntents/SetNumberValueIntentHandler.swift deleted file mode 100644 index 6b901cee2..000000000 --- a/openHABIntents/SetNumberValueIntentHandler.swift +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class SetNumberValueIntentHandler: NSObject, OpenHABSetNumberValueIntentHandling { - func resolveHome(for intent: OpenHABSetNumberValueIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetNumberValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideItemOptionsCollection(for intent: OpenHABSetNumberValueIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.number]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetNumberValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.number]) - } - - func confirm(intent: OpenHABSetNumberValueIntent) async -> OpenHABSetNumberValueIntentResponse { - OpenHABSetNumberValueIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetNumberValueIntent) async -> OpenHABSetNumberValueIntentResponse { - Logger.intentHandling.info("SetNumberValueIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard let value = intent.value else { - return .failureEmptyValue(item: itemName) - } - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: value.stringValue) - - return .success(value: value, item: itemName) - } -} diff --git a/openHABIntents/SetStringValueIntentHandler.swift b/openHABIntents/SetStringValueIntentHandler.swift deleted file mode 100644 index c94d9dd88..000000000 --- a/openHABIntents/SetStringValueIntentHandler.swift +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os.log - -class SetStringValueIntentHandler: NSObject, OpenHABSetStringValueIntentHandling { - func resolveHome(for intent: OpenHABSetStringValueIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetStringValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideItemOptionsCollection(for intent: OpenHABSetStringValueIntent, searchTerm: String?) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.stringItem]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetStringValueIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.stringItem]) - } - - func confirm(intent: OpenHABSetStringValueIntent) async -> OpenHABSetStringValueIntentResponse { - OpenHABSetStringValueIntentResponse(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetStringValueIntent) async -> OpenHABSetStringValueIntentResponse { - Logger.intentHandling.info("SetStringValueIntent for \(intent.item ?? "")") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard let value = intent.value else { - return .failureEmptyValue(item: itemName) - } - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: value) - - return .success(value: value, item: itemName) - } -} diff --git a/openHABIntents/SetSwitchStateIntentHandler.swift b/openHABIntents/SetSwitchStateIntentHandler.swift deleted file mode 100644 index 7d47f2b7b..000000000 --- a/openHABIntents/SetSwitchStateIntentHandler.swift +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -import Foundation -import Intents -import OpenHABCore -import os - -final class SetSwitchStateIntentHandler: NSObject, OpenHABSetSwitchStateIntentHandling { - private static let onLabel = String(localized: "on", comment: "").capitalized - private static let offLabel = String(localized: "off", comment: "").capitalized - - private static let localizedActions = [onLabel, offLabel] - private static let actionMap: [String: String] = [ - onLabel: "ON", - offLabel: "OFF" - ] - - func resolveHome(for intent: OpenHABSetSwitchStateIntent) async -> OpenHABHomeResolutionResult { - Logger.intentHandling.info("Resolving home for intent: \(intent)") - return await OpenHABIntentHelper.resolveHome(home: intent.home, item: intent.item) - } - - func provideHomeOptionsCollection(for intent: OpenHABSetSwitchStateIntent) async throws -> INObjectCollection { - await OpenHABIntentHelper.getHomeOptions() - } - - func provideActionOptionsCollection(for intent: OpenHABSetSwitchStateIntent) async throws -> INObjectCollection { - Logger.intentHandling.info("SetSwitchStateIntentHandler provideActionOptionsCollection") - return INObjectCollection(items: Self.localizedActions as [NSString]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetSwitchStateIntent, searchTerm: String?) async throws -> INObjectCollection { - Logger.intentHandling.info("SetSwitchStateIntentHandler provideItemOptionsCollection with searchTerm: \(searchTerm ?? "", privacy: .public)") - return await OpenHABIntentHelper.getItemOptions(home: intent.home, searchTerm: searchTerm, itemTypes: [.switchItem]) - } - - func provideItemOptionsCollection(for intent: OpenHABSetSwitchStateIntent) async throws -> INObjectCollection { - Logger.intentHandling.info("SetSwitchStateIntentHandler provideItemOptionsCollection") - return await OpenHABIntentHelper.getItemOptions(home: intent.home, itemTypes: [.switchItem]) - } - - func confirm(intent: OpenHABSetSwitchStateIntent) async -> OpenHABSetSwitchStateIntentResponse { - .init(code: .ready, userActivity: nil) - } - - func handle(intent: OpenHABSetSwitchStateIntent) async -> OpenHABSetSwitchStateIntentResponse { - Logger.intentHandling.info("SetSwitchStateIntent for item: \(intent.item ?? "", privacy: .public)") - - guard let itemName = intent.item, let home = intent.home else { - return .failureInvalidItem( - String(localized: "empty.itemorhome", defaultValue: "Empty Item or empty Home", comment: "Empty Item or empty Home") - ) - } - - guard let homeId = home.uuid, await Preferences.shared.storedHomes[homeId] != nil else { - return .failureInvalidItem(String(localized: "unknownHome", comment: "unknown home")) - } - - guard !itemName.isEmpty else { - return .failureInvalidItem(String(localized: "empty.itemname", defaultValue: "Empty Item name", comment: "empty item name")) - } - - guard let action = intent.action else { - return .failureInvalidAction(String(localized: "empty.action", defaultValue: "Empty action", comment: "empty action"), item: itemName) - } - - guard let command = Self.actionMap[action] else { - return .failureInvalidAction(action, item: itemName) - } - - guard let items = await OpenHABItemCache.instance.getCachedItem(name: itemName, home: homeId), !items.isEmpty else { - return .failureInvalidItem(itemName) - } - - let item = items[0] - - await OpenHABItemCache.instance.sendCommand(to: item, home: homeId, command: command) - return .success(action: action, item: itemName) - } -} diff --git a/openHABIntents/de.lproj/Intents.strings b/openHABIntents/de.lproj/Intents.strings deleted file mode 100644 index 58514a3f5..000000000 --- a/openHABIntents/de.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"1GaCDV" = "Aktion"; - -"1Kf0qe" = "Farbwert festlegen"; - -"1SeS3V" = "${item} auf ${value} (HSB) setzen"; - -"1USOx9" = "Item"; - -"1cBKdm" = "${item} auf ${value} setzen"; - -"2q0TdY" = "${item} kann nicht gefunden werden"; - -"3LMXV8" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"3evvza" = "Wert"; - -"4WCzFG" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"5B4e9l" = "Setze die Zeichenkette eines String-Kontrollelements"; - -"5Uu4bK" = "Erhalte Status eines Items"; - -"67qFTP" = "Ungültiger leerer Wert für ${item}"; - -"68Y2Ya" = "Dimmer/Rollladen Name"; - -"6hUiXZ" = "Der Status von ${item} ist ${state}"; - -"71MWtI" = "Zuhause"; - -"7BE642" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"7GA2x6" = "Zuhause"; - -"80K2Dt" = "${item} kann nicht gefunden werden"; - -"8OBnvT" = "Ungültiger leerer Wert für ${item}"; - -"8dhboX" = "Wert"; - -"9Pu17E" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"CZfT5a" = "Der Wert ${value} wurde an ${item} gesendet"; - -"Cdg015" = "${item} auf ${value} setzen"; - -"Ch9Akw" = "${item} auf ${value} setzen"; - -"DxO2wk" = "Farbe eines Kontrollelements festlegen"; - -"E07xjp" = "${item} auf ${value} (HSB) setzen"; - -"E291x3" = "Wert"; - -"EEj00Q" = "${item} auf ${value} setzen"; - -"Fb0pUB" = "Aktion ${action} gesendet um ${item} zu schalten"; - -"GD4RTw" = "Aktuellen Status eines Items abrufen"; - -"GI0faP" = "Status ungültig: ${state} für ${item}"; - -"Gjd9MH" = "${item} auf ${value} setzen"; - -"H26PEQ" = "Der Status von ${item} wurde auf ${state} gesetzt"; - -"H2V4UV" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"HGbUGw" = "Wert"; - -"IS2nO0" = "Name des Zuhauses"; - -"IpbXHW" = "${action} an ${item} senden"; - -"JUznKl" = "Der String ${value} wurde an ${item} gesendet"; - -"Jn2aNw" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "${item} auf ${value} setzen"; - -"MIqlld" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"N5YsnB" = "${item} kann nicht gefunden werden"; - -"Ncumus" = "Name des Zuhauses"; - -"NehcMF" = "Objektname"; - -"NjP70V" = "${item} kann nicht gefunden werden"; - -"Nqxuw0" = "Status"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "${item} kann nicht gefunden werden"; - -"P9kblb" = "Name des Zuhauses"; - -"QATq1i" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"SMYYBh" = "${item} auf ${value} setzen"; - -"TJ1p5Y" = "Aktion"; - -"TK8Rrn" = "Der Farbwert ${value} wurde an ${item} gesendet"; - -"UZYwuH" = "Wert eines Dimmers oder Rolladens festlegen"; - -"V0QsN4" = "Ungültiger Wert ${value} für ${item} (0-100)"; - -"VEApie" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "${item} auf ${value} (HSB) setzen"; - -"WsMKz2" = "Setze den Status von ${item} auf ${state}"; - -"X9cN9h" = "Zuhause"; - -"XMNs7r" = "Schalterzustand setzen"; - -"YFIIBn" = "Name des Zuhauses"; - -"YUoeAL" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"ZuBsOg" = "Zuhause"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Wert"; - -"eFNxrT" = "Name des Zuhauses"; - -"eFb7vM" = "Status eines Kontakts öffnen oder schließen"; - -"exrmKW" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"gBn7U6" = "String Kontrollwert setzen"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"h91G9C" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"hflKDd" = "${item} kann nicht gefunden werden"; - -"i8fP5e" = "Aktion"; - -"iBrALm" = "Zuhause"; - -"iCUEet" = "Aktion ungültig: ${action} für ${item}"; - -"iKSmqU" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"iRjffC" = "Ungültiger leerer Wert für ${item}"; - -"jP4VSa" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"jbUbYw" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"jyXazc" = "${action} an ${item} senden"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "Zuhause"; - -"lLFYtM" = "Es gibt ${count} konfigurierte Zuhause mit einem Item namens '${item}'."; - -"llL4sP" = "Wert"; - -"lqlrGx" = "Name wechseln"; - -"m5DFXq" = "Status"; - -"m9yAKr" = "Erhalte Status von ${item}"; - -"mAKsWc" = "Wert"; - -"mHbznY" = "Für welches Zuhause möchten Sie den Wert abrufen?"; - -"mVz5mJ" = "${item} auf ${value} setzen"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "Status"; - -"nzw2UT" = "Wert des Kontaktstatus festlegen"; - -"o35DYr" = "Wert eines numerischen Kontrollelements setzen"; - -"oOjAU0" = "Setze den Status eines Schalters ein oder aus"; - -"oUolm5" = "${action} an ${item} senden"; - -"pg4Bec" = "Festlegen des Wertes eines Dimmers oder Rollladens"; - -"pptfkD" = "Dezimalwert eines numerischen Kontrollelements setzen"; - -"ptGHON" = "${item} kann nicht gefunden werden"; - -"qYPBPC" = "${item} auf ${value} setzen"; - -"qu9K9v" = "Setze den Status von ${item} auf ${state}"; - -"rVXwR2" = "Name des Zuhauses"; - -"raIAR6" = "Ungültiger Wert: ${value} für ${item} muss HSB sein (0-360,0-100,0-100)"; - -"rsrXB9" = "Name des Zuhauses"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Zur Bestätigung, meinten Sie '${home}'?"; - -"ttiBzN" = "Setze den Status von ${item} auf ${state}"; - -"tuvmDw" = "Wert"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Name des Zuhauses"; - -"wm0fqx" = "Der Farbwert ${value} wurde an ${item} gesendet"; - -"zLlY3g" = "Zuhause"; - -"zV2TH2" = "Zuhause"; diff --git a/openHABIntents/en.lproj/Intents.strings b/openHABIntents/en.lproj/Intents.strings deleted file mode 100644 index 45f12fe6f..000000000 --- a/openHABIntents/en.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Just to confirm, you wanted '${home}'?"; - -"1GaCDV" = "Action"; - -"1Kf0qe" = "Set Color Control Value"; - -"1SeS3V" = "Set ${item} to ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Set ${item} to ${value}"; - -"2q0TdY" = "Sorry can't find ${item}"; - -"3LMXV8" = "For which home do you want to get the value?"; - -"3evvza" = "Value"; - -"4WCzFG" = "For which home do you want to get the value?"; - -"5B4e9l" = "Set the string of a string control item"; - -"5Uu4bK" = "Get Item State"; - -"67qFTP" = "Invalid empty value for ${item}"; - -"68Y2Ya" = "Dimmer/Roller Name"; - -"6hUiXZ" = "The state of ${item} is ${state}"; - -"71MWtI" = "home"; - -"7BE642" = "There are ${count} configured homes with an item named '${item}'."; - -"7GA2x6" = "home"; - -"80K2Dt" = "Sorry can't find ${item}"; - -"8OBnvT" = "Invalid empty value for ${item}"; - -"8dhboX" = "Value"; - -"9Pu17E" = "For which home do you want to get the value?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "There are ${count} configured homes with an item named '${item}'."; - -"CZfT5a" = "Sent the number ${value} to ${item}"; - -"Cdg015" = "Set ${item} to ${value}"; - -"Ch9Akw" = "Set ${item} to ${value}"; - -"DxO2wk" = "Set the color of a color control item"; - -"E07xjp" = "Set ${item} to ${value} (HSB)"; - -"E291x3" = "Value"; - -"EEj00Q" = "Set ${item} to ${value}"; - -"Fb0pUB" = "Sent the action of ${action} to switch ${item}"; - -"GD4RTw" = "Retrieve the current state of an item"; - -"GI0faP" = "State invalid: ${state} for ${item}"; - -"Gjd9MH" = "Set ${item} to ${value}"; - -"H26PEQ" = "The state of ${item} was set to ${state}"; - -"H2V4UV" = "There are ${count} configured homes with an item named '${item}'."; - -"HGbUGw" = "Value"; - -"IS2nO0" = "Home name"; - -"IpbXHW" = "Send ${action} to ${item}"; - -"JUznKl" = "Sent the string ${value} to ${item}"; - -"Jn2aNw" = "Just to confirm, you wanted '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Set ${item} to ${value}"; - -"MIqlld" = "There are ${count} configured homes with an item named '${item}'."; - -"N5YsnB" = "Sorry can't find ${item}"; - -"Ncumus" = "Home name"; - -"NehcMF" = "Item Name"; - -"NjP70V" = "Sorry can't find ${item}"; - -"Nqxuw0" = "State"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Sorry can't find ${item}"; - -"P9kblb" = "Home name"; - -"QATq1i" = "For which home do you want to get the value?"; - -"SMYYBh" = "Set ${item} to ${value}"; - -"TJ1p5Y" = "Action"; - -"TK8Rrn" = "Sent the color value of ${value} to ${item}"; - -"UZYwuH" = "Set Dimmer or Roller Shutter Value"; - -"V0QsN4" = "Invalid value ${value} for ${item} (0-100)"; - -"VEApie" = "Just to confirm, you wanted '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Set ${item} to ${value} (HSB)"; - -"WsMKz2" = "Set the state of ${item} to ${state}"; - -"X9cN9h" = "home"; - -"XMNs7r" = "Set Switch State"; - -"YFIIBn" = "Home name"; - -"YUoeAL" = "There are ${count} configured homes with an item named '${item}'."; - -"ZuBsOg" = "home"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Value"; - -"eFNxrT" = "Home name"; - -"eFb7vM" = "Set the state of a contact open or closed"; - -"exrmKW" = "Just to confirm, you wanted '${home}'?"; - -"gBn7U6" = "Set String Control Value"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Just to confirm, you wanted '${home}'?"; - -"h91G9C" = "For which home do you want to get the value?"; - -"hflKDd" = "Sorry can't find ${item}"; - -"i8fP5e" = "Action"; - -"iBrALm" = "home"; - -"iCUEet" = "Action invalid: ${action} for ${item}"; - -"iKSmqU" = "Just to confirm, you wanted '${home}'?"; - -"iRjffC" = "Invalid empty value for ${item}"; - -"jP4VSa" = "There are ${count} configured homes with an item named '${item}'."; - -"jbUbYw" = "For which home do you want to get the value?"; - -"jyXazc" = "Send ${action} to ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "home"; - -"lLFYtM" = "There are ${count} configured homes with an item named '${item}'."; - -"llL4sP" = "Value"; - -"lqlrGx" = "Switch name"; - -"m5DFXq" = "State"; - -"m9yAKr" = "Get ${item} State"; - -"mAKsWc" = "Value"; - -"mHbznY" = "For which home do you want to get the value?"; - -"mVz5mJ" = "Set ${item} to ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "State"; - -"nzw2UT" = "Set Contact State Value"; - -"o35DYr" = "Set Number Control Value"; - -"oOjAU0" = "Set the state of a switch on or off"; - -"oUolm5" = "Send ${action} to ${item}"; - -"pg4Bec" = "Set the integer value of a dimmer or roller shutter"; - -"pptfkD" = "Set the decimal value of a number control item"; - -"ptGHON" = "Sorry can't find ${item}"; - -"qYPBPC" = "Set ${item} to ${value}"; - -"qu9K9v" = "Set the state of ${item} to ${state}"; - -"rVXwR2" = "Home name"; - -"raIAR6" = "Invalid value: ${value} for ${item} must be HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Home name"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Just to confirm, you wanted '${home}'?"; - -"ttiBzN" = "Set the state of ${item} to ${state}"; - -"tuvmDw" = "Value"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Home name"; - -"wm0fqx" = "Sent the value of ${value} to ${item}"; - -"zLlY3g" = "home"; - -"zV2TH2" = "Home"; diff --git a/openHABIntents/es.lproj/Intents.strings b/openHABIntents/es.lproj/Intents.strings deleted file mode 100644 index 0206fde6e..000000000 --- a/openHABIntents/es.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Para confirmar, ¿quisiste '${home}'?"; - -"1GaCDV" = "Acción"; - -"1Kf0qe" = "Define el valor de control de color"; - -"1SeS3V" = "Establecer ${item} en ${value} (HSB)"; - -"1USOx9" = "Ítem"; - -"1cBKdm" = "Establecer ${item} en ${value}"; - -"2q0TdY" = "Lo sentimos, no se puede encontrar ${item}"; - -"3LMXV8" = "¿Para qué hogar deseas obtener el valor?"; - -"3evvza" = "Valor"; - -"4WCzFG" = "¿Para qué hogar deseas obtener el valor?"; - -"5B4e9l" = "Establecer la cadena de un ítem controlador de cadena"; - -"5Uu4bK" = "Obtener estado del ítem"; - -"67qFTP" = "Valor vacío no válido para ${item}"; - -"68Y2Ya" = "Nombre regulador/persiana"; - -"6hUiXZ" = "El estado de ${item} es ${state}"; - -"71MWtI" = "hogar"; - -"7BE642" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"7GA2x6" = "hogar"; - -"80K2Dt" = "Lo sentimos, no se puede encontrar ${item}"; - -"8OBnvT" = "Valor vacío no válido para ${item}"; - -"8dhboX" = "Valor"; - -"9Pu17E" = "¿Para qué hogar deseas obtener el valor?"; - -"B2I9b5" = "Ítem"; - -"BaNYuS" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"CZfT5a" = "Definir el número ${value} a ${item}"; - -"Cdg015" = "Establecer ${item} en ${value}"; - -"Ch9Akw" = "Establecer ${item} en ${value}"; - -"DxO2wk" = "Definir el color de un ítem de control de color"; - -"E07xjp" = "Establecer ${item} en ${value} (HSB)"; - -"E291x3" = "Valor"; - -"EEj00Q" = "Establecer ${item} en ${value}"; - -"Fb0pUB" = "Enviada la acción de ${action} al interruptor ${item}"; - -"GD4RTw" = "Recuperar el estado actual de un elemento"; - -"GI0faP" = "Estado no válido: ${state} para ${item}"; - -"Gjd9MH" = "Establecer ${item} en ${value}"; - -"H26PEQ" = "El estado de ${item} se ha establecido en ${state}"; - -"H2V4UV" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"HGbUGw" = "Valor"; - -"IS2nO0" = "Nombre del hogar"; - -"IpbXHW" = "Enviar ${action} a ${item}"; - -"JUznKl" = "Enviada la cadena ${value} a ${item}"; - -"Jn2aNw" = "Para confirmar, ¿quisiste '${home}'?"; - -"KXqi2s" = "Ítem"; - -"M8mX5O" = "Ítem"; - -"MCp4x6" = "Ítem"; - -"MGYSky" = "Establecer ${item} en ${value}"; - -"MIqlld" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"N5YsnB" = "Lo sentimos, no se puede encontrar ${item}"; - -"Ncumus" = "Nombre del hogar"; - -"NehcMF" = "Nombre del ítem"; - -"NjP70V" = "Lo sentimos, no se puede encontrar ${item}"; - -"Nqxuw0" = "Estado"; - -"Nuhhxp" = "Ítem"; - -"Oazi8d" = "Lo sentimos, no se puede encontrar ${item}"; - -"P9kblb" = "Nombre del hogar"; - -"QATq1i" = "¿Para qué hogar deseas obtener el valor?"; - -"SMYYBh" = "Establecer ${item} en ${value}"; - -"TJ1p5Y" = "Acción"; - -"TK8Rrn" = "Enviado el valor de color de ${value} a ${item}"; - -"UZYwuH" = "Definir el valor del regulador o persiana"; - -"V0QsN4" = "Valor no válido ${value} para ${item} (0-100)"; - -"VEApie" = "Para confirmar, ¿quisiste '${home}'?"; - -"VTeXc1" = "Ítem"; - -"VmFkwo" = "Establecer ${item} en ${value} (HSB)"; - -"WsMKz2" = "Establecer el estado de ${item} a ${state}"; - -"X9cN9h" = "hogar"; - -"XMNs7r" = "Definir el estado del interruptor"; - -"YFIIBn" = "Nombre del hogar"; - -"YUoeAL" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"ZuBsOg" = "hogar"; - -"cBrxnz" = "Ítem"; - -"dtRamI" = "Valor"; - -"eFNxrT" = "Nombre del hogar"; - -"eFb7vM" = "Establecer el estado de un interruptor encendido o apagado"; - -"exrmKW" = "Para confirmar, ¿quisiste '${home}'?"; - -"gBn7U6" = "Establecer el valor de control de cadena"; - -"gQzHiG" = "Ítem"; - -"gobcZi" = "Para confirmar, ¿quisiste '${home}'?"; - -"h91G9C" = "¿Para qué hogar deseas obtener el valor?"; - -"hflKDd" = "Lo sentimos, no se puede encontrar ${item}"; - -"i8fP5e" = "Acción"; - -"iBrALm" = "hogar"; - -"iCUEet" = "Acción inválida: ${action} para ${item}"; - -"iKSmqU" = "Para confirmar, ¿quisiste '${home}'?"; - -"iRjffC" = "Valor vacío no válido para ${item}"; - -"jP4VSa" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"jbUbYw" = "¿Para qué hogar deseas obtener el valor?"; - -"jyXazc" = "Enviar ${action} a ${item}"; - -"k0eXWc" = "Ítem"; - -"k6pj4o" = "hogar"; - -"lLFYtM" = "Hay ${count} hogares configurados con un ítem llamado '${item}'."; - -"llL4sP" = "Valor"; - -"lqlrGx" = "Nombre del interruptor"; - -"m5DFXq" = "Estado"; - -"m9yAKr" = "Obtener Estado de ${item}"; - -"mAKsWc" = "Valor"; - -"mHbznY" = "¿Para qué hogar deseas obtener el valor?"; - -"mVz5mJ" = "Establecer ${item} en ${value}"; - -"mpxNDZ" = "Ítem"; - -"n7cmvU" = "Estado"; - -"nzw2UT" = "Establecer el valor del estado del pulsador"; - -"o35DYr" = "Establecer valor de control de número"; - -"oOjAU0" = "Establecer el estado de un interruptor encendido o apagado"; - -"oUolm5" = "Enviar ${action} a ${item}"; - -"pg4Bec" = "Define el valor entero de un regulador o persiana"; - -"pptfkD" = "Establecer el valor decimal de un ítem de control numérico"; - -"ptGHON" = "Lo sentimos, no se puede encontrar ${item}"; - -"qYPBPC" = "Establecer ${item} en ${value}"; - -"qu9K9v" = "Establecer el estado de ${item} a ${state}"; - -"rVXwR2" = "Nombre del hogar"; - -"raIAR6" = "Valor no válido: ${value} para ${item} debe ser HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Nombre del hogar"; - -"skEQxZ" = "Ítem"; - -"tclrCp" = "Ítem"; - -"tkWfxz" = "Para confirmar, ¿quisiste '${home}'?"; - -"ttiBzN" = "Establecer el estado de ${item} a ${state}"; - -"tuvmDw" = "Valor"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Nombre del hogar"; - -"wm0fqx" = "Enviado el valor de ${value} a ${item}"; - -"zLlY3g" = "hogar"; - -"zV2TH2" = "Hogar"; diff --git a/openHABIntents/fi.lproj/Intents.strings b/openHABIntents/fi.lproj/Intents.strings deleted file mode 100644 index 45f12fe6f..000000000 --- a/openHABIntents/fi.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Just to confirm, you wanted '${home}'?"; - -"1GaCDV" = "Action"; - -"1Kf0qe" = "Set Color Control Value"; - -"1SeS3V" = "Set ${item} to ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Set ${item} to ${value}"; - -"2q0TdY" = "Sorry can't find ${item}"; - -"3LMXV8" = "For which home do you want to get the value?"; - -"3evvza" = "Value"; - -"4WCzFG" = "For which home do you want to get the value?"; - -"5B4e9l" = "Set the string of a string control item"; - -"5Uu4bK" = "Get Item State"; - -"67qFTP" = "Invalid empty value for ${item}"; - -"68Y2Ya" = "Dimmer/Roller Name"; - -"6hUiXZ" = "The state of ${item} is ${state}"; - -"71MWtI" = "home"; - -"7BE642" = "There are ${count} configured homes with an item named '${item}'."; - -"7GA2x6" = "home"; - -"80K2Dt" = "Sorry can't find ${item}"; - -"8OBnvT" = "Invalid empty value for ${item}"; - -"8dhboX" = "Value"; - -"9Pu17E" = "For which home do you want to get the value?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "There are ${count} configured homes with an item named '${item}'."; - -"CZfT5a" = "Sent the number ${value} to ${item}"; - -"Cdg015" = "Set ${item} to ${value}"; - -"Ch9Akw" = "Set ${item} to ${value}"; - -"DxO2wk" = "Set the color of a color control item"; - -"E07xjp" = "Set ${item} to ${value} (HSB)"; - -"E291x3" = "Value"; - -"EEj00Q" = "Set ${item} to ${value}"; - -"Fb0pUB" = "Sent the action of ${action} to switch ${item}"; - -"GD4RTw" = "Retrieve the current state of an item"; - -"GI0faP" = "State invalid: ${state} for ${item}"; - -"Gjd9MH" = "Set ${item} to ${value}"; - -"H26PEQ" = "The state of ${item} was set to ${state}"; - -"H2V4UV" = "There are ${count} configured homes with an item named '${item}'."; - -"HGbUGw" = "Value"; - -"IS2nO0" = "Home name"; - -"IpbXHW" = "Send ${action} to ${item}"; - -"JUznKl" = "Sent the string ${value} to ${item}"; - -"Jn2aNw" = "Just to confirm, you wanted '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Set ${item} to ${value}"; - -"MIqlld" = "There are ${count} configured homes with an item named '${item}'."; - -"N5YsnB" = "Sorry can't find ${item}"; - -"Ncumus" = "Home name"; - -"NehcMF" = "Item Name"; - -"NjP70V" = "Sorry can't find ${item}"; - -"Nqxuw0" = "State"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Sorry can't find ${item}"; - -"P9kblb" = "Home name"; - -"QATq1i" = "For which home do you want to get the value?"; - -"SMYYBh" = "Set ${item} to ${value}"; - -"TJ1p5Y" = "Action"; - -"TK8Rrn" = "Sent the color value of ${value} to ${item}"; - -"UZYwuH" = "Set Dimmer or Roller Shutter Value"; - -"V0QsN4" = "Invalid value ${value} for ${item} (0-100)"; - -"VEApie" = "Just to confirm, you wanted '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Set ${item} to ${value} (HSB)"; - -"WsMKz2" = "Set the state of ${item} to ${state}"; - -"X9cN9h" = "home"; - -"XMNs7r" = "Set Switch State"; - -"YFIIBn" = "Home name"; - -"YUoeAL" = "There are ${count} configured homes with an item named '${item}'."; - -"ZuBsOg" = "home"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Value"; - -"eFNxrT" = "Home name"; - -"eFb7vM" = "Set the state of a contact open or closed"; - -"exrmKW" = "Just to confirm, you wanted '${home}'?"; - -"gBn7U6" = "Set String Control Value"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Just to confirm, you wanted '${home}'?"; - -"h91G9C" = "For which home do you want to get the value?"; - -"hflKDd" = "Sorry can't find ${item}"; - -"i8fP5e" = "Action"; - -"iBrALm" = "home"; - -"iCUEet" = "Action invalid: ${action} for ${item}"; - -"iKSmqU" = "Just to confirm, you wanted '${home}'?"; - -"iRjffC" = "Invalid empty value for ${item}"; - -"jP4VSa" = "There are ${count} configured homes with an item named '${item}'."; - -"jbUbYw" = "For which home do you want to get the value?"; - -"jyXazc" = "Send ${action} to ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "home"; - -"lLFYtM" = "There are ${count} configured homes with an item named '${item}'."; - -"llL4sP" = "Value"; - -"lqlrGx" = "Switch name"; - -"m5DFXq" = "State"; - -"m9yAKr" = "Get ${item} State"; - -"mAKsWc" = "Value"; - -"mHbznY" = "For which home do you want to get the value?"; - -"mVz5mJ" = "Set ${item} to ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "State"; - -"nzw2UT" = "Set Contact State Value"; - -"o35DYr" = "Set Number Control Value"; - -"oOjAU0" = "Set the state of a switch on or off"; - -"oUolm5" = "Send ${action} to ${item}"; - -"pg4Bec" = "Set the integer value of a dimmer or roller shutter"; - -"pptfkD" = "Set the decimal value of a number control item"; - -"ptGHON" = "Sorry can't find ${item}"; - -"qYPBPC" = "Set ${item} to ${value}"; - -"qu9K9v" = "Set the state of ${item} to ${state}"; - -"rVXwR2" = "Home name"; - -"raIAR6" = "Invalid value: ${value} for ${item} must be HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Home name"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Just to confirm, you wanted '${home}'?"; - -"ttiBzN" = "Set the state of ${item} to ${state}"; - -"tuvmDw" = "Value"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Home name"; - -"wm0fqx" = "Sent the value of ${value} to ${item}"; - -"zLlY3g" = "home"; - -"zV2TH2" = "Home"; diff --git a/openHABIntents/fr.lproj/Intents.strings b/openHABIntents/fr.lproj/Intents.strings deleted file mode 100644 index b24db5eed..000000000 --- a/openHABIntents/fr.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Pour confirmer, vous vouliez '${home}' ?"; - -"1GaCDV" = "Action"; - -"1Kf0qe" = "Définir la valeur de contrôle de couleur"; - -"1SeS3V" = "Définir ${item} à ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Définir ${item} à ${value}"; - -"2q0TdY" = "Désolé impossible de trouver ${item}"; - -"3LMXV8" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"3evvza" = "Valeur"; - -"4WCzFG" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"5B4e9l" = "Définir la chaîne d'un item de contrôle de chaîne"; - -"5Uu4bK" = "Récupérer l'état de l'item"; - -"67qFTP" = "Valeur vide invalide pour ${item}"; - -"68Y2Ya" = "Nom du Dimmer/Roller"; - -"6hUiXZ" = "L'état de ${item} est ${state}"; - -"71MWtI" = "maison"; - -"7BE642" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"7GA2x6" = "maison"; - -"80K2Dt" = "Désolé impossible de trouver ${item}"; - -"8OBnvT" = "Valeur vide invalide pour ${item}"; - -"8dhboX" = "Valeur"; - -"9Pu17E" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"CZfT5a" = "Envoyé le numéro ${value} à ${item}"; - -"Cdg015" = "Définir ${item} à ${value}"; - -"Ch9Akw" = "Définir ${item} à ${value}"; - -"DxO2wk" = "Définir la couleur d'un élément de contrôle de couleur"; - -"E07xjp" = "Définir ${item} à ${value} (HSB)"; - -"E291x3" = "Valeur"; - -"EEj00Q" = "Définir ${item} à ${value}"; - -"Fb0pUB" = "A envoyé l'action ${action} pour changer la position de ${item}"; - -"GD4RTw" = "Récupérer l'état actuel d'un item"; - -"GI0faP" = "État invalide : ${state} pour ${item}"; - -"Gjd9MH" = "Définir ${item} à ${value}"; - -"H26PEQ" = "L'état de ${item} a été réglé sur ${state}"; - -"H2V4UV" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"HGbUGw" = "Valeur"; - -"IS2nO0" = "Nom de la maison"; - -"IpbXHW" = "Envoyer ${action} à ${item}"; - -"JUznKl" = "Envoi de la chaîne ${value} à ${item}"; - -"Jn2aNw" = "Pour confirmer, vous vouliez '${home}' ?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Définir ${item} à ${value}"; - -"MIqlld" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"N5YsnB" = "Désolé impossible de trouver ${item}"; - -"Ncumus" = "Nom de la maison"; - -"NehcMF" = "Nom d'Item"; - -"NjP70V" = "Désolé impossible de trouver ${item}"; - -"Nqxuw0" = "État"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Désolé impossible de trouver ${item}"; - -"P9kblb" = "Nom de la maison"; - -"QATq1i" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"SMYYBh" = "Définir ${item} à ${value}"; - -"TJ1p5Y" = "Action"; - -"TK8Rrn" = "Envoyé la valeur de couleur de ${value} à ${item}"; - -"UZYwuH" = "Régler la valeur du Dimmer ou du Volet Roulant"; - -"V0QsN4" = "Valeur ${value} invalide pour ${item} (0-100)"; - -"VEApie" = "Pour confirmer, vous vouliez '${home}' ?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Définir ${item} à ${value} (HSB)"; - -"WsMKz2" = "Définit l'état de ${item} à ${state}"; - -"X9cN9h" = "maison"; - -"XMNs7r" = "Définir l'état du Switch"; - -"YFIIBn" = "Nom de la maison"; - -"YUoeAL" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"ZuBsOg" = "maison"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Valeur"; - -"eFNxrT" = "Nom de la maison"; - -"eFb7vM" = "Définir l'état d'un contacteur ouvert ou fermé"; - -"exrmKW" = "Pour confirmer, vous vouliez '${home}' ?"; - -"gBn7U6" = "Définir la valeur de contrôle de chaîne"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Pour confirmer, vous vouliez '${home}' ?"; - -"h91G9C" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"hflKDd" = "Désolé impossible de trouver ${item}"; - -"i8fP5e" = "Action"; - -"iBrALm" = "maison"; - -"iCUEet" = "Action invalide : ${action} pour ${item}"; - -"iKSmqU" = "Pour confirmer, vous vouliez '${home}' ?"; - -"iRjffC" = "Valeur vide invalide pour ${item}"; - -"jP4VSa" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"jbUbYw" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"jyXazc" = "Envoyer ${action} à ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "maison"; - -"lLFYtM" = "Il y a ${count} maisons configurées avec un élément nommé '${item}'."; - -"llL4sP" = "Valeur"; - -"lqlrGx" = "Nom du Switch"; - -"m5DFXq" = "État"; - -"m9yAKr" = "Récupérer l'état de ${item}"; - -"mAKsWc" = "Valeur"; - -"mHbznY" = "Pour quelle maison voulez-vous obtenir la valeur ?"; - -"mVz5mJ" = "Définir ${item} à ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "État"; - -"nzw2UT" = "Définir la valeur de l'état du contacteur"; - -"o35DYr" = "Définir la valeur de contrôle du numéro"; - -"oOjAU0" = "Définir l'état d'un contacteur ouvert ou fermé"; - -"oUolm5" = "Envoyer ${action} à ${item}"; - -"pg4Bec" = "Définir la valeur entière d'un dimmer ou d'un volet roulant"; - -"pptfkD" = "Définir la valeur décimale d'un item de contrôle de nombre"; - -"ptGHON" = "Désolé impossible de trouver ${item}"; - -"qYPBPC" = "Définir ${item} à ${value}"; - -"qu9K9v" = "Définit l'état de ${item} à ${state}"; - -"rVXwR2" = "Nom de la maison"; - -"raIAR6" = "Valeur invalide : ${value} pour ${item} doit être HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Nom de la maison"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Pour confirmer, vous vouliez '${home}' ?"; - -"ttiBzN" = "Définit l'état de ${item} à ${state}"; - -"tuvmDw" = "Valeur"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Nom de la maison"; - -"wm0fqx" = "Envoyé la valeur ${value} à ${item}"; - -"zLlY3g" = "maison"; - -"zV2TH2" = "Maison"; diff --git a/openHABIntents/it.lproj/Intents.strings b/openHABIntents/it.lproj/Intents.strings deleted file mode 100644 index 77ca98408..000000000 --- a/openHABIntents/it.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Per confermare, volevi '${home}'?"; - -"1GaCDV" = "Azione"; - -"1Kf0qe" = "Imposta Valore Controllo Colore"; - -"1SeS3V" = "Imposta ${item} a ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Imposta ${item} a ${value}"; - -"2q0TdY" = "Spiacente non posso trovare ${item}"; - -"3LMXV8" = "Per quale casa vuoi ottenere il valore?"; - -"3evvza" = "Valore"; - -"4WCzFG" = "Per quale casa vuoi ottenere il valore?"; - -"5B4e9l" = "Imposta il valore di una item di controllo di tipo stringa"; - -"5Uu4bK" = "Leggi lo stato dell’Item"; - -"67qFTP" = "Valore vuoto non valido per ${item}"; - -"68Y2Ya" = "Nome Dimmer/Roller"; - -"6hUiXZ" = "Lo stato di ${item} è ${state}"; - -"71MWtI" = "casa"; - -"7BE642" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"7GA2x6" = "casa"; - -"80K2Dt" = "Spiacente non posso trovare ${item}"; - -"8OBnvT" = "Valore vuoto non valido per ${item}"; - -"8dhboX" = "Valore"; - -"9Pu17E" = "Per quale casa vuoi ottenere il valore?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"CZfT5a" = "Inviato il numero ${value} a ${item}"; - -"Cdg015" = "Imposta ${item} a ${value}"; - -"Ch9Akw" = "Imposta ${item} a ${value}"; - -"DxO2wk" = "Imposta il colore di un Item di controllo colore"; - -"E07xjp" = "Imposta ${item} a ${value} (HSB)"; - -"E291x3" = "Valore"; - -"EEj00Q" = "Imposta ${item} a ${value}"; - -"Fb0pUB" = "Inviata l'azione di ${action} per cambiare ${item}"; - -"GD4RTw" = "Recupera lo stato attuale di un Item"; - -"GI0faP" = "Stato non valido: ${state} per ${item}"; - -"Gjd9MH" = "Imposta ${item} a ${value}"; - -"H26PEQ" = "Lo stato di ${item} è stato impostato su ${state}"; - -"H2V4UV" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"HGbUGw" = "Valore"; - -"IS2nO0" = "Nome della casa"; - -"IpbXHW" = "Invia ${action} a ${item}"; - -"JUznKl" = "Inviata la stringa ${value} a ${item}"; - -"Jn2aNw" = "Per confermare, volevi '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Imposta ${item} a ${value}"; - -"MIqlld" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"N5YsnB" = "Spiacente non posso trovare ${item}"; - -"Ncumus" = "Nome della casa"; - -"NehcMF" = "Nome Item"; - -"NjP70V" = "Spiacente non posso trovare ${item}"; - -"Nqxuw0" = "Stato"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Spiacente non posso trovare ${item}"; - -"P9kblb" = "Nome della casa"; - -"QATq1i" = "Per quale casa vuoi ottenere il valore?"; - -"SMYYBh" = "Imposta ${item} a ${value}"; - -"TJ1p5Y" = "Azione"; - -"TK8Rrn" = "Inviato il valore di colore di ${value} a ${item}"; - -"UZYwuH" = "Imposta il valore di Dimmer o Tapparella"; - -"V0QsN4" = "Valore non valido ${value} per ${item} (0-100)"; - -"VEApie" = "Per confermare, volevi '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Imposta ${item} a ${value} (HSB)"; - -"WsMKz2" = "Imposta lo stato di ${item} a ${state}"; - -"X9cN9h" = "casa"; - -"XMNs7r" = "Imposta stato Interruttore"; - -"YFIIBn" = "Nome della casa"; - -"YUoeAL" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"ZuBsOg" = "casa"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Valore"; - -"eFNxrT" = "Nome della casa"; - -"eFb7vM" = "Imposta lo stato di un contatto aperto o chiuso"; - -"exrmKW" = "Per confermare, volevi '${home}'?"; - -"gBn7U6" = "Imposta Valore della Stringa di Controllo"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Per confermare, volevi '${home}'?"; - -"h91G9C" = "Per quale casa vuoi ottenere il valore?"; - -"hflKDd" = "Spiacente non posso trovare ${item}"; - -"i8fP5e" = "Azione"; - -"iBrALm" = "casa"; - -"iCUEet" = "Azione non valida: ${action} per ${item}"; - -"iKSmqU" = "Per confermare, volevi '${home}'?"; - -"iRjffC" = "Valore vuoto non valido per ${item}"; - -"jP4VSa" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"jbUbYw" = "Per quale casa vuoi ottenere il valore?"; - -"jyXazc" = "Invia ${action} a ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "casa"; - -"lLFYtM" = "Ci sono ${count} case configurate con un elemento chiamato '${item}'."; - -"llL4sP" = "Valore"; - -"lqlrGx" = "Cambia nome"; - -"m5DFXq" = "Stato"; - -"m9yAKr" = "Leggi lo stato di ${item}"; - -"mAKsWc" = "Valore"; - -"mHbznY" = "Per quale casa vuoi ottenere il valore?"; - -"mVz5mJ" = "Imposta ${item} a ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "Stato"; - -"nzw2UT" = "Imposta lo Stato del Contatto"; - -"o35DYr" = "Imposta Valore del Numero di Controllo"; - -"oOjAU0" = "Imposta lo stato di un interruttore ad acceso o spento"; - -"oUolm5" = "Invia ${action} a ${item}"; - -"pg4Bec" = "Imposta il valore intero di un dimmer o una tapparella"; - -"pptfkD" = "Imposta il valore decimale di un Item di controllo numerico"; - -"ptGHON" = "Spiacente non posso trovare ${item}"; - -"qYPBPC" = "Imposta ${item} a ${value}"; - -"qu9K9v" = "Imposta lo stato di ${item} a ${state}"; - -"rVXwR2" = "Nome della casa"; - -"raIAR6" = "Valore non valido: ${value} per ${item} deve essere HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Nome della casa"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Per confermare, volevi '${home}'?"; - -"ttiBzN" = "Imposta lo stato di ${item} a ${state}"; - -"tuvmDw" = "Valore"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Nome della casa"; - -"wm0fqx" = "Imposta il valore di ${value} a ${item}"; - -"zLlY3g" = "casa"; - -"zV2TH2" = "Casa"; diff --git a/openHABIntents/nb.lproj/Intents.strings b/openHABIntents/nb.lproj/Intents.strings deleted file mode 100644 index 3560993b6..000000000 --- a/openHABIntents/nb.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "For å bekrefte, mente du '${home}'?"; - -"1GaCDV" = "Handling"; - -"1Kf0qe" = "Angi Fargekontrollverdi"; - -"1SeS3V" = "Sett ${item} til ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Sett ${item} til ${value}"; - -"2q0TdY" = "Beklager, kan ikke finne ${item}"; - -"3LMXV8" = "For hvilket hjem vil du hente verdien?"; - -"3evvza" = "Verdi"; - -"4WCzFG" = "For hvilket hjem vil du hente verdien?"; - -"5B4e9l" = "Angi teksten til et teksterkontroll-Item"; - -"5Uu4bK" = "Få Item-tilstand"; - -"67qFTP" = "Ugyldig tom verdi for ${item}"; - -"68Y2Ya" = "Navn på Dimmer/Ruller"; - -"6hUiXZ" = "Tilstanden til ${item} er ${state}"; - -"71MWtI" = "hjem"; - -"7BE642" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"7GA2x6" = "hjem"; - -"80K2Dt" = "Beklager, kan ikke finne ${item}"; - -"8OBnvT" = "Ugyldig tom verdi for ${item}"; - -"8dhboX" = "Verdi"; - -"9Pu17E" = "For hvilket hjem vil du hente verdien?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"CZfT5a" = "Send den numeriske verdien ${value} til ${item}"; - -"Cdg015" = "Sett ${item} til ${value}"; - -"Ch9Akw" = "Sett ${item} til ${value}"; - -"DxO2wk" = "Angi fargen til et fargekontroll-Item"; - -"E07xjp" = "Sett ${item} til ${value} (HSB)"; - -"E291x3" = "Verdi"; - -"EEj00Q" = "Sett ${item} til ${value}"; - -"Fb0pUB" = "Send handlingen ${action} til bryter ${item}"; - -"GD4RTw" = "Hent nåværende tilstand for et Item"; - -"GI0faP" = "Ugyldig tilstand: ${state} for ${item}"; - -"Gjd9MH" = "Sett ${item} til ${value}"; - -"H26PEQ" = "Tilstanden til ${item} ble satt til ${state}"; - -"H2V4UV" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"HGbUGw" = "Verdi"; - -"IS2nO0" = "Hjemnavn"; - -"IpbXHW" = "Send ${action} til ${item}"; - -"JUznKl" = "Send strengverdien ${value} til ${item}"; - -"Jn2aNw" = "For å bekrefte, mente du '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Sett ${item} til ${value}"; - -"MIqlld" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"N5YsnB" = "Beklager, kan ikke finne ${item}"; - -"Ncumus" = "Hjemnavn"; - -"NehcMF" = "Item-navn"; - -"NjP70V" = "Beklager, kan ikke finne ${item}"; - -"Nqxuw0" = "Tilstand"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Beklager, kan ikke finne ${item}"; - -"P9kblb" = "Hjemnavn"; - -"QATq1i" = "For hvilket hjem vil du hente verdien?"; - -"SMYYBh" = "Sett ${item} til ${value}"; - -"TJ1p5Y" = "Handling"; - -"TK8Rrn" = "Sendte fargeverdien ${value} til ${item}"; - -"UZYwuH" = "Angi Verdi for Dimmer eller Rullegardin"; - -"V0QsN4" = "Ugyldig verdi ${value} for ${item} (0-100)"; - -"VEApie" = "For å bekrefte, mente du '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Sett ${item} til ${value} (HSB)"; - -"WsMKz2" = "Sett tilstanden til ${item} til ${state}"; - -"X9cN9h" = "hjem"; - -"XMNs7r" = "Angi Brytertilstand"; - -"YFIIBn" = "Hjemnavn"; - -"YUoeAL" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"ZuBsOg" = "hjem"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Verdi"; - -"eFNxrT" = "Hjemnavn"; - -"eFb7vM" = "Sett tilstanden til en bryter til åpen eller lukket"; - -"exrmKW" = "For å bekrefte, mente du '${home}'?"; - -"gBn7U6" = "Sett Strengkontroll-Verdi"; - -"gQzHiG" = "Item"; - -"gobcZi" = "For å bekrefte, mente du '${home}'?"; - -"h91G9C" = "For hvilket hjem vil du hente verdien?"; - -"hflKDd" = "Beklager, kan ikke finne ${item}"; - -"i8fP5e" = "Handling"; - -"iBrALm" = "hjem"; - -"iCUEet" = "Ugyldig handling: ${action} for ${item}"; - -"iKSmqU" = "For å bekrefte, mente du '${home}'?"; - -"iRjffC" = "Ugyldig tom verdi for ${item}"; - -"jP4VSa" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"jbUbYw" = "For hvilket hjem vil du hente verdien?"; - -"jyXazc" = "Send ${action} til ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "hjem"; - -"lLFYtM" = "Det er ${count} konfigurerte hjem med et element kalt '${item}'."; - -"llL4sP" = "Verdi"; - -"lqlrGx" = "Navn på bryter"; - -"m5DFXq" = "Tilstand"; - -"m9yAKr" = "Hent tilstand til ${item}"; - -"mAKsWc" = "Verdi"; - -"mHbznY" = "For hvilket hjem vil du hente verdien?"; - -"mVz5mJ" = "Sett ${item} til ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "Tilstand"; - -"nzw2UT" = "Sett Tilstand for Bryter"; - -"o35DYr" = "Sett Verdi for Numerisk Kontroll"; - -"oOjAU0" = "Sett tilstanden til en bryter til av eller på"; - -"oUolm5" = "Send ${action} til ${item}"; - -"pg4Bec" = "Sett heltallsverdien til en dimmer eller rullegardin"; - -"pptfkD" = "Sett desimalverdien til en numerisk kontroll-Item"; - -"ptGHON" = "Beklager, kan ikke finne ${item}"; - -"qYPBPC" = "Sett ${item} til ${value}"; - -"qu9K9v" = "Sett tilstanden til ${item} til ${state}"; - -"rVXwR2" = "Hjemnavn"; - -"raIAR6" = "Ugyldig verdi: ${value} for ${item} må være HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Hjemnavn"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "For å bekrefte, mente du '${home}'?"; - -"ttiBzN" = "Sett tilstanden til ${item} til ${state}"; - -"tuvmDw" = "Verdi"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Hjemnavn"; - -"wm0fqx" = "Sendte verdien ${value} til ${item}"; - -"zLlY3g" = "hjem"; - -"zV2TH2" = "Hjem"; diff --git a/openHABIntents/nl.lproj/Intents.strings b/openHABIntents/nl.lproj/Intents.strings deleted file mode 100644 index f43ceb97c..000000000 --- a/openHABIntents/nl.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Ter bevestiging, u bedoelde '${home}'?"; - -"1GaCDV" = "Actie"; - -"1Kf0qe" = "Kleurwaarde instellen"; - -"1SeS3V" = "Stel ${item} in op ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Stel ${item} in op ${value}"; - -"2q0TdY" = "Sorry kan ${item} niet vinden"; - -"3LMXV8" = "Voor welk huis wilt u de waarde ophalen?"; - -"3evvza" = "Waarde"; - -"4WCzFG" = "Voor welk huis wilt u de waarde ophalen?"; - -"5B4e9l" = "De tekenreeks van een string controle item instellen"; - -"5Uu4bK" = "Haal item state op"; - -"67qFTP" = "Ongeldige lege waarde voor ${item}"; - -"68Y2Ya" = "Dimmer/Roller Naam"; - -"6hUiXZ" = "De staat van ${item} is ${state}"; - -"71MWtI" = "huis"; - -"7BE642" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"7GA2x6" = "huis"; - -"80K2Dt" = "Sorry kan ${item} niet vinden"; - -"8OBnvT" = "Ongeldige lege waarde voor ${item}"; - -"8dhboX" = "Waarde"; - -"9Pu17E" = "Voor welk huis wilt u de waarde ophalen?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"CZfT5a" = "De waarde ${value} is verzonden naar ${item}"; - -"Cdg015" = "Stel ${item} in op ${value}"; - -"Ch9Akw" = "Stel ${item} in op ${value}"; - -"DxO2wk" = "Stel de kleur van een kleurbesturingsitem in"; - -"E07xjp" = "Stel ${item} in op ${value} (HSB)"; - -"E291x3" = "Waarde"; - -"EEj00Q" = "Stel ${item} in op ${value}"; - -"Fb0pUB" = "Actie ${action} verzonden om ${item} om te zetten"; - -"GD4RTw" = "Haal de huidige status van een item op"; - -"GI0faP" = "Status ongeldig: ${state} voor ${item}"; - -"Gjd9MH" = "Stel ${item} in op ${value}"; - -"H26PEQ" = "De status van ${item} is ingesteld op ${state}"; - -"H2V4UV" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"HGbUGw" = "Waarde"; - -"IS2nO0" = "Huisnaam"; - -"IpbXHW" = "${action} verzenden naar ${item}"; - -"JUznKl" = "De waarde ${value} is verzonden naar ${item}"; - -"Jn2aNw" = "Ter bevestiging, u bedoelde '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Stel ${item} in op ${value}"; - -"MIqlld" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"N5YsnB" = "Sorry kan ${item} niet vinden"; - -"Ncumus" = "Huisnaam"; - -"NehcMF" = "Item Naam"; - -"NjP70V" = "Sorry kan ${item} niet vinden"; - -"Nqxuw0" = "Status"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Sorry kan ${item} niet vinden"; - -"P9kblb" = "Huisnaam"; - -"QATq1i" = "Voor welk huis wilt u de waarde ophalen?"; - -"SMYYBh" = "Stel ${item} in op ${value}"; - -"TJ1p5Y" = "Actie"; - -"TK8Rrn" = "De kleurwaarde van ${value} is verzonden naar ${item}"; - -"UZYwuH" = "Stel Dimmer of Rolluik waarde in"; - -"V0QsN4" = "Ongeldige waarde ${value} voor ${item} (0-100)"; - -"VEApie" = "Ter bevestiging, u bedoelde '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Stel ${item} in op ${value} (HSB)"; - -"WsMKz2" = "Zet de status van ${item} op ${state}"; - -"X9cN9h" = "huis"; - -"XMNs7r" = "Stel schakelstatus in"; - -"YFIIBn" = "Huisnaam"; - -"YUoeAL" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"ZuBsOg" = "huis"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Waarde"; - -"eFNxrT" = "Huisnaam"; - -"eFb7vM" = "Zet de status van een contact open of gesloten"; - -"exrmKW" = "Ter bevestiging, u bedoelde '${home}'?"; - -"gBn7U6" = "Zet String Control Waarde in"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Ter bevestiging, u bedoelde '${home}'?"; - -"h91G9C" = "Voor welk huis wilt u de waarde ophalen?"; - -"hflKDd" = "Sorry kan ${item} niet vinden"; - -"i8fP5e" = "Actie"; - -"iBrALm" = "huis"; - -"iCUEet" = "Status ongeldig: ${action} voor ${item}"; - -"iKSmqU" = "Ter bevestiging, u bedoelde '${home}'?"; - -"iRjffC" = "Ongeldige lege waarde voor ${item}"; - -"jP4VSa" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"jbUbYw" = "Voor welk huis wilt u de waarde ophalen?"; - -"jyXazc" = "${action} verzenden naar ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "huis"; - -"lLFYtM" = "Er zijn ${count} geconfigureerde huizen met een item genaamd '${item}'."; - -"llL4sP" = "Waarde"; - -"lqlrGx" = "Wissel naam"; - -"m5DFXq" = "Status"; - -"m9yAKr" = "Haal ${item} state op"; - -"mAKsWc" = "Waarde"; - -"mHbznY" = "Voor welk huis wilt u de waarde ophalen?"; - -"mVz5mJ" = "Stel ${item} in op ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "Status"; - -"nzw2UT" = "Stel contact status waarde in"; - -"o35DYr" = "Zet Nummer Control Waarde"; - -"oOjAU0" = "Zet de status van een schakelaar aan of uit"; - -"oUolm5" = "${action} verzenden naar ${item}"; - -"pg4Bec" = "Zet de integerwaarde van een dimmer of rolluik"; - -"pptfkD" = "Stel de decimale waarde van een nummer control item in"; - -"ptGHON" = "Sorry kan ${item} niet vinden"; - -"qYPBPC" = "Stel ${item} in op ${value}"; - -"qu9K9v" = "Zet de status van ${item} op ${state}"; - -"rVXwR2" = "Huisnaam"; - -"raIAR6" = "Ongeldige waarde: ${value} voor ${item} moet HSB zijn (0-360,0-100,0-100)"; - -"rsrXB9" = "Huisnaam"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Ter bevestiging, u bedoelde '${home}'?"; - -"ttiBzN" = "Zet de status van ${item} op ${state}"; - -"tuvmDw" = "Waarde"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Huisnaam"; - -"wm0fqx" = "De waarde van ${value} is verzonden naar ${item}"; - -"zLlY3g" = "huis"; - -"zV2TH2" = "Huis"; diff --git a/openHABIntents/openHABIntents.entitlements b/openHABIntents/openHABIntents.entitlements deleted file mode 100644 index 029463f3a..000000000 --- a/openHABIntents/openHABIntents.entitlements +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.application-groups - - group.org.openhab.app - - keychain-access-groups - - $(AppIdentifierPrefix)org.openhab.app - - - diff --git a/openHABIntents/ru.lproj/Intents.strings b/openHABIntents/ru.lproj/Intents.strings deleted file mode 100644 index 45f12fe6f..000000000 --- a/openHABIntents/ru.lproj/Intents.strings +++ /dev/null @@ -1,243 +0,0 @@ -"0hYnWM" = "Just to confirm, you wanted '${home}'?"; - -"1GaCDV" = "Action"; - -"1Kf0qe" = "Set Color Control Value"; - -"1SeS3V" = "Set ${item} to ${value} (HSB)"; - -"1USOx9" = "Item"; - -"1cBKdm" = "Set ${item} to ${value}"; - -"2q0TdY" = "Sorry can't find ${item}"; - -"3LMXV8" = "For which home do you want to get the value?"; - -"3evvza" = "Value"; - -"4WCzFG" = "For which home do you want to get the value?"; - -"5B4e9l" = "Set the string of a string control item"; - -"5Uu4bK" = "Get Item State"; - -"67qFTP" = "Invalid empty value for ${item}"; - -"68Y2Ya" = "Dimmer/Roller Name"; - -"6hUiXZ" = "The state of ${item} is ${state}"; - -"71MWtI" = "home"; - -"7BE642" = "There are ${count} configured homes with an item named '${item}'."; - -"7GA2x6" = "home"; - -"80K2Dt" = "Sorry can't find ${item}"; - -"8OBnvT" = "Invalid empty value for ${item}"; - -"8dhboX" = "Value"; - -"9Pu17E" = "For which home do you want to get the value?"; - -"B2I9b5" = "Item"; - -"BaNYuS" = "There are ${count} configured homes with an item named '${item}'."; - -"CZfT5a" = "Sent the number ${value} to ${item}"; - -"Cdg015" = "Set ${item} to ${value}"; - -"Ch9Akw" = "Set ${item} to ${value}"; - -"DxO2wk" = "Set the color of a color control item"; - -"E07xjp" = "Set ${item} to ${value} (HSB)"; - -"E291x3" = "Value"; - -"EEj00Q" = "Set ${item} to ${value}"; - -"Fb0pUB" = "Sent the action of ${action} to switch ${item}"; - -"GD4RTw" = "Retrieve the current state of an item"; - -"GI0faP" = "State invalid: ${state} for ${item}"; - -"Gjd9MH" = "Set ${item} to ${value}"; - -"H26PEQ" = "The state of ${item} was set to ${state}"; - -"H2V4UV" = "There are ${count} configured homes with an item named '${item}'."; - -"HGbUGw" = "Value"; - -"IS2nO0" = "Home name"; - -"IpbXHW" = "Send ${action} to ${item}"; - -"JUznKl" = "Sent the string ${value} to ${item}"; - -"Jn2aNw" = "Just to confirm, you wanted '${home}'?"; - -"KXqi2s" = "Item"; - -"M8mX5O" = "Item"; - -"MCp4x6" = "Item"; - -"MGYSky" = "Set ${item} to ${value}"; - -"MIqlld" = "There are ${count} configured homes with an item named '${item}'."; - -"N5YsnB" = "Sorry can't find ${item}"; - -"Ncumus" = "Home name"; - -"NehcMF" = "Item Name"; - -"NjP70V" = "Sorry can't find ${item}"; - -"Nqxuw0" = "State"; - -"Nuhhxp" = "Item"; - -"Oazi8d" = "Sorry can't find ${item}"; - -"P9kblb" = "Home name"; - -"QATq1i" = "For which home do you want to get the value?"; - -"SMYYBh" = "Set ${item} to ${value}"; - -"TJ1p5Y" = "Action"; - -"TK8Rrn" = "Sent the color value of ${value} to ${item}"; - -"UZYwuH" = "Set Dimmer or Roller Shutter Value"; - -"V0QsN4" = "Invalid value ${value} for ${item} (0-100)"; - -"VEApie" = "Just to confirm, you wanted '${home}'?"; - -"VTeXc1" = "Item"; - -"VmFkwo" = "Set ${item} to ${value} (HSB)"; - -"WsMKz2" = "Set the state of ${item} to ${state}"; - -"X9cN9h" = "home"; - -"XMNs7r" = "Set Switch State"; - -"YFIIBn" = "Home name"; - -"YUoeAL" = "There are ${count} configured homes with an item named '${item}'."; - -"ZuBsOg" = "home"; - -"cBrxnz" = "Item"; - -"dtRamI" = "Value"; - -"eFNxrT" = "Home name"; - -"eFb7vM" = "Set the state of a contact open or closed"; - -"exrmKW" = "Just to confirm, you wanted '${home}'?"; - -"gBn7U6" = "Set String Control Value"; - -"gQzHiG" = "Item"; - -"gobcZi" = "Just to confirm, you wanted '${home}'?"; - -"h91G9C" = "For which home do you want to get the value?"; - -"hflKDd" = "Sorry can't find ${item}"; - -"i8fP5e" = "Action"; - -"iBrALm" = "home"; - -"iCUEet" = "Action invalid: ${action} for ${item}"; - -"iKSmqU" = "Just to confirm, you wanted '${home}'?"; - -"iRjffC" = "Invalid empty value for ${item}"; - -"jP4VSa" = "There are ${count} configured homes with an item named '${item}'."; - -"jbUbYw" = "For which home do you want to get the value?"; - -"jyXazc" = "Send ${action} to ${item}"; - -"k0eXWc" = "Item"; - -"k6pj4o" = "home"; - -"lLFYtM" = "There are ${count} configured homes with an item named '${item}'."; - -"llL4sP" = "Value"; - -"lqlrGx" = "Switch name"; - -"m5DFXq" = "State"; - -"m9yAKr" = "Get ${item} State"; - -"mAKsWc" = "Value"; - -"mHbznY" = "For which home do you want to get the value?"; - -"mVz5mJ" = "Set ${item} to ${value}"; - -"mpxNDZ" = "Item"; - -"n7cmvU" = "State"; - -"nzw2UT" = "Set Contact State Value"; - -"o35DYr" = "Set Number Control Value"; - -"oOjAU0" = "Set the state of a switch on or off"; - -"oUolm5" = "Send ${action} to ${item}"; - -"pg4Bec" = "Set the integer value of a dimmer or roller shutter"; - -"pptfkD" = "Set the decimal value of a number control item"; - -"ptGHON" = "Sorry can't find ${item}"; - -"qYPBPC" = "Set ${item} to ${value}"; - -"qu9K9v" = "Set the state of ${item} to ${state}"; - -"rVXwR2" = "Home name"; - -"raIAR6" = "Invalid value: ${value} for ${item} must be HSB (0-360,0-100,0-100)"; - -"rsrXB9" = "Home name"; - -"skEQxZ" = "Item"; - -"tclrCp" = "Item"; - -"tkWfxz" = "Just to confirm, you wanted '${home}'?"; - -"ttiBzN" = "Set the state of ${item} to ${state}"; - -"tuvmDw" = "Value"; - -"uGsyyj" = "240,100,100"; - -"v3hsWV" = "Home name"; - -"wm0fqx" = "Sent the value of ${value} to ${item}"; - -"zLlY3g" = "home"; - -"zV2TH2" = "Home"; diff --git a/openHABIntentsTests/SetSwitchStateIntentHandlerTests.swift b/openHABIntentsTests/SetSwitchStateIntentHandlerTests.swift deleted file mode 100644 index 5b50ee05a..000000000 --- a/openHABIntentsTests/SetSwitchStateIntentHandlerTests.swift +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2010-2026 Contributors to the openHAB project -// -// See the NOTICE file(s) distributed with this work for additional -// information. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0 -// -// SPDX-License-Identifier: EPL-2.0 - -//// Copyright (c) 2010-2025 Contributors to the openHAB project -//// -//// See the NOTICE file(s) distributed with this work for additional -//// information. -//// -//// This program and the accompanying materials are made available under the -//// terms of the Eclipse Public License 2.0 which is available at -//// http://www.eclipse.org/legal/epl-2.0 -//// -//// SPDX-License-Identifier: EPL-2.0 -// -// import XCTest -// @testable import openHAB // Replace with actual module name -// import Intents -// import OpenHABCore -// -// final class SetSwitchStateIntentHandlerTests: XCTestCase { -// -// final class MockItemCache: ItemCacheProtocol { -// var lastCommand: String? -// -// func getItem(name: String) async -> OpenHABItem? { -// .mockSwitch(name: name) -// } -// -// func sendCommand(_ item: OpenHABItem, commandToSend: String) async { -// lastCommand = commandToSend -// } -// -// func getItemNames(searchTerm: String?, types: [OpenHABItem.ItemType]?) -> [String] { -// return ["MockSwitch"] -// } -// } -// -// func testHandleIntentMissingItem() { -// let intent = OpenHABSetSwitchStateIntent() -// intent.item = nil -// intent.action = "On" -// -// let handler = SetSwitchStateIntentHandler(itemCache: MockItemCache()) -// let expectation = expectation(description: "Handle failure") -// -// handler.handle(intent: intent) { response in -// XCTAssertEqual(response.code, .failureInvalidItem) -// expectation.fulfill() -// } -// -// wait(for: [expectation], timeout: 1) -// } -// } -// -// extension OpenHABItem { -// static func mockSwitch(name: String = "MockSwitch", state: String = "OFF") -> OpenHABItem { -// return OpenHABItem( -// name: name, -// type: "Switch", -// state: state, -// link: "http://mock/api/items/\(name)", -// label: name, -// groupType: nil, -// stateDescription: nil, -// commandDescription: nil, -// members: [], -// category: nil, -// options: nil -// ) -// } -// } diff --git a/openHABTestsSwift/AppIntentsTests.swift b/openHABTestsSwift/AppIntentsTests.swift new file mode 100644 index 000000000..e5476a215 --- /dev/null +++ b/openHABTestsSwift/AppIntentsTests.swift @@ -0,0 +1,341 @@ +// Copyright (c) 2010-2026 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import AppIntents +import Foundation +@testable import openHAB +import OpenHABCore +import Testing + +// MARK: - Test Helpers + +private func makeItem(name: String = "TestItem", type: String, label: String = "Test Item") -> OpenHABItem { + OpenHABItem( + name: name, + type: type, + state: nil, + link: "", + label: label, + groupType: nil, + stateDescription: nil, + commandDescription: nil, + members: [], + category: nil, + options: nil + ) +} + +// MARK: - Color Value Validation + +@Suite("SetColorValueIntent validation") +struct SetColorValueIntentTests { + let homeId = UUID() + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + private func makeIntent(home: Home? = nil, value: String = "240,100,100") -> SetColorValueIntent { + let defaultHome = Home(id: homeId.uuidString, displayString: "Test Home") + let entity = ColorItemEntity(makeItem(type: "Color", label: "Color Light"), homeId: homeId) + let i = SetColorValueIntent() + i.home = home ?? defaultHome + i.itemEntity = entity + i.value = value + return i + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func homeMismatchThrowsItemNotInHome() async { + let wrongHome = Home(id: UUID().uuidString, displayString: "Wrong Home") + await #expect(throws: ColorValueError.self) { + try await makeIntent(home: wrongHome).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func twoComponentsThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "240,100").perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func hueAbove360ThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "361,100,100").perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func negativeHueThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "-1,100,100").perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func saturationAbove100ThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "240,101,100").perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func brightnessAbove100ThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "240,100,101").perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func nonNumericComponentThrowsInvalidValue() async { + await #expect(throws: ColorValueError.self) { + try await makeIntent(value: "blue,100,100").perform() + } + } +} + +// MARK: - Dimmer / Roller Range Validation + +@Suite("SetDimmerRollerValueIntent validation") +struct SetDimmerRollerValueIntentTests { + let homeId = UUID() + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + private func makeIntent(home: Home? = nil, value: Int = 50) -> SetDimmerRollerValueIntent { + let defaultHome = Home(id: homeId.uuidString, displayString: "Test Home") + let entity = DimmerItemEntity(makeItem(type: "Dimmer", label: "Dimmer Light"), homeId: homeId) + let i = SetDimmerRollerValueIntent() + i.home = home ?? defaultHome + i.itemEntity = entity + i.value = value + return i + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func homeMismatchThrowsItemNotInHome() async { + let wrongHome = Home(id: UUID().uuidString, displayString: "Wrong Home") + await #expect(throws: DimmerRollerValueError.self) { + try await makeIntent(home: wrongHome).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func valueAbove100ThrowsInvalidValue() async { + await #expect(throws: DimmerRollerValueError.self) { + try await makeIntent(value: 101).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func valueBelow0ThrowsInvalidValue() async { + await #expect(throws: DimmerRollerValueError.self) { + try await makeIntent(value: -1).perform() + } + } +} + +// MARK: - Location Coordinate Validation + +@Suite("SetLocationValueIntent validation") +struct SetLocationValueIntentTests { + let homeId = UUID() + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + private func makeIntent(home: Home? = nil, latitude: Double = 48.0, longitude: Double = 11.0) -> SetLocationValueIntent { + let defaultHome = Home(id: homeId.uuidString, displayString: "Test Home") + let entity = LocationItemEntity(makeItem(type: "Location", label: "My Location"), homeId: homeId) + let intent = SetLocationValueIntent() + intent.home = home ?? defaultHome + intent.itemEntity = entity + intent.latitude = latitude + intent.longitude = longitude + return intent + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func homeMismatchThrowsItemNotInHome() async { + let wrongHome = Home(id: UUID().uuidString, displayString: "Wrong Home") + await #expect(throws: LocationValueError.self) { + try await makeIntent(home: wrongHome).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func latitudeAbove90ThrowsInvalidLatitude() async { + await #expect(throws: LocationValueError.self) { + try await makeIntent(latitude: 90.001).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func latitudeBelow90ThrowsInvalidLatitude() async { + await #expect(throws: LocationValueError.self) { + try await makeIntent(latitude: -90.001).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func longitudeAbove180ThrowsInvalidLongitude() async { + await #expect(throws: LocationValueError.self) { + try await makeIntent(latitude: 0, longitude: 180.001).perform() + } + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func longitudeBelow180ThrowsInvalidLongitude() async { + await #expect(throws: LocationValueError.self) { + try await makeIntent(latitude: 0, longitude: -180.001).perform() + } + } +} + +// MARK: - Switch Home Membership Validation + +@Suite("SetSwitchItemIntent validation") +struct SetSwitchItemIntentTests { + let homeId = UUID() + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func homeMismatchThrowsItemNotInHome() async { + let wrongHome = Home(id: UUID().uuidString, displayString: "Wrong Home") + let entity = SwitchItemEntity(makeItem(type: "Switch", label: "My Switch"), homeId: homeId) + let intent = SetSwitchItemIntent() + intent.home = wrongHome + intent.itemEntity = entity + intent.action = .on + await #expect(throws: ControlItemError.self) { + try await intent.perform() + } + } +} + +// MARK: - Home Resolution + +@Suite("HomeResolver") +struct HomeResolverTests { + let homeId = UUID() + + @Test func optionalHomeUsesItemHomeId() throws { + let resolved = try HomeResolver.resolvedHomeId( + selectedHome: nil, + itemHomeId: homeId, + itemLabel: "Test Item", + mismatchError: ItemStateError.itemNotInHome + ) + + #expect(resolved == homeId) + } + + @Test func selectedHomeMismatchThrows() throws { + let wrongHome = Home(id: UUID().uuidString, displayString: "Wrong Home") + + #expect(throws: ItemStateError.self) { + try HomeResolver.resolvedHomeId( + selectedHome: wrongHome, + itemHomeId: homeId, + itemLabel: "Test Item", + mismatchError: ItemStateError.itemNotInHome + ) + } + } + + @Test func invalidSelectedHomeIdentifierThrows() throws { + let invalidHome = Home(id: "not-a-uuid", displayString: "Broken Home") + + #expect(throws: HomeResolutionError.self) { + try HomeResolver.resolvedHomeId( + selectedHome: invalidHome, + itemHomeId: homeId, + itemLabel: "Test Item", + mismatchError: ItemStateError.itemNotInHome + ) + } + } + + @Test func singleStoredHomeBecomesDefault() async throws { + let resolved = try await HomeResolver.resolveHomeId( + selectedHome: nil, + itemName: "TestItem", + listStoredHomes: { [homeId] }, + exactMatchedHomes: { [] } + ) + + #expect(resolved == homeId) + } + + @Test func uniqueExactMatchChoosesHome() async throws { + let otherHomeId = UUID() + + let resolved = try await HomeResolver.resolveHomeId( + selectedHome: nil, + itemName: "TestItem", + listStoredHomes: { [homeId, otherHomeId] }, + exactMatchedHomes: { [otherHomeId] } + ) + + #expect(resolved == otherHomeId) + } + + @Test func ambiguousHomesRequireSelection() async { + let otherHomeId = UUID() + + await #expect(throws: HomeResolutionError.self) { + try await HomeResolver.resolveHomeId( + selectedHome: nil, + itemName: "TestItem", + listStoredHomes: { [homeId, otherHomeId] }, + exactMatchedHomes: { [homeId, otherHomeId] } + ) + } + } +} + +// MARK: - Error Descriptions + +@Suite("Intent error descriptions") +struct IntentErrorDescriptionTests { + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func colorItemNotInHomeContainsItemAndHomeName() { + let error = ColorValueError.itemNotInHome("Color Light", "My Home") + let str = String(localized: error.localizedStringResource) + #expect(str.contains("Color Light")) + #expect(str.contains("My Home")) + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func colorInvalidValueContainsValueAndItemName() { + let error = ColorValueError.invalidValue("blue,100,100", "Color Light") + let str = String(localized: error.localizedStringResource) + #expect(str.contains("blue,100,100")) + #expect(str.contains("Color Light")) + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func dimmerInvalidValueContainsValueAndItemName() { + let error = DimmerRollerValueError.invalidValue(150, "My Dimmer") + let str = String(localized: error.localizedStringResource) + #expect(str.contains("150")) + #expect(str.contains("My Dimmer")) + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func locationInvalidLatitudeDescriptionContains90() { + let error = LocationValueError.invalidLatitude + let str = String(localized: error.localizedStringResource) + #expect(str.contains("90")) + } + + @available(iOS 17.0, macOS 14.0, watchOS 10.0, *) + @Test func locationInvalidLongitudeDescriptionContains180() { + let error = LocationValueError.invalidLongitude + let str = String(localized: error.localizedStringResource) + #expect(str.contains("180")) + } +} diff --git a/openHABTestsSwift/LocalizationTests.swift b/openHABTestsSwift/LocalizationTests.swift index c7acedf03..5466db13c 100644 --- a/openHABTestsSwift/LocalizationTests.swift +++ b/openHABTestsSwift/LocalizationTests.swift @@ -14,14 +14,81 @@ import Foundation import Testing +private final class BundleLocator: AnyObject {} + struct LocalizationTests { + private struct StringCatalog: Decodable { + let sourceLanguage: String + let strings: [String: Entry] + + struct Entry: Decodable { + let comment: String? + let localizations: [String: Localization]? + let shouldTranslate: Bool? + } + + struct Localization: Decodable { + let stringUnit: StringUnit? + } + + struct StringUnit: Decodable { + let state: String + let value: String + } + } + private static var localizations: [String] { Bundle.main.localizations.filter { $0 != "Base" } } - private static var intentsBundle: Bundle? { - guard let pluginsURL = Bundle.main.builtInPlugInsURL else { return nil } - return Bundle(url: pluginsURL.appendingPathComponent("openHABIntents.appex")) + private static var stringCatalogURL: URL? { + // Primary: bundle resource (avoids #filePath sandbox restrictions in iOS Simulator) + if let url = Bundle(for: BundleLocator.self) + .url(forResource: "LocalizableTestCatalog", withExtension: "json") { + return url + } + // Fallback: source-relative path + let url = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("openHAB/Supporting Files/Localizable.xcstrings") + return FileManager.default.fileExists(atPath: url.path) ? url : nil + } + + private static var stringCatalog: StringCatalog? { + guard let url = stringCatalogURL, + let data = try? Data(contentsOf: url) else { + return nil + } + + return try? JSONDecoder().decode(StringCatalog.self, from: data) + } + + private static var appIntentEntries: [String: StringCatalog.Entry] { + guard let stringCatalog else { + return [:] + } + + return stringCatalog.strings.filter { key, entry in + key.contains("${") || (entry.comment?.localizedCaseInsensitiveContains("app intent") == true) + } + } + + private static func sourceValue(for key: String, entry: StringCatalog.Entry, sourceLanguage: String) -> String { + entry.localizations?[sourceLanguage]?.stringUnit?.value.nonEmpty ?? key + } + + private static func translatedValue(for language: String, entry: StringCatalog.Entry) -> String? { + guard let localization = entry.localizations?[language]?.stringUnit else { + return nil + } + + return localization.state == "translated" ? localization.value.nonEmpty : nil + } + + private static func placeholders(in value: String) -> [String] { + let regex = #/\$\{([a-z0-9]*)\}/#.ignoresCase() + return value.matches(of: regex).map { String($0.0) } } // MARK: - Tests @@ -43,82 +110,69 @@ struct LocalizationTests { } @Test func intentsLocalizations() { - guard let bundle = LocalizationTests.intentsBundle, - let path = bundle.url(forResource: "Intents", withExtension: "strings", subdirectory: nil, localization: "en"), - let localizableStrings = NSDictionary(contentsOf: path) as? [String: String], - !localizableStrings.isEmpty + guard let stringCatalog = LocalizationTests.stringCatalog, + !LocalizationTests.appIntentEntries.isEmpty else { - Issue.record("Failed to load Intents.strings.") + Issue.record("Failed to load App Intents entries from Localizable.xcstrings.") return } - let localizations = bundle.localizations.filter { $0 != "Base" } + for (key, entry) in LocalizationTests.appIntentEntries { + let sourceValue = LocalizationTests.sourceValue(for: key, entry: entry, sourceLanguage: stringCatalog.sourceLanguage) + #expect(sourceValue.isEmpty == false, "Missing source localization for App Intents key '\(key)'.") - for language in localizations { - print("Testing language: '\(language)'.") + for language in LocalizationTests.localizations where language != stringCatalog.sourceLanguage { + guard let translation = LocalizationTests.translatedValue(for: language, entry: entry) else { + continue + } - for localizableString in localizableStrings { - let translation = localizableString.key.localized(for: language, with: "Intents", in: bundle) - #expect(translation != nil, "Failed to get translation for key '\(localizableString.key)' in language '\(language)'.") - #expect(translation != "__MISSING__", "Missing translation for key '\(localizableString.key)' in language '\(language)'.") - #expect(translation?.isEmpty == false, "Translation for key '\(localizableString.key)' in language '\(language)' is empty.") - print("Translation: \(localizableString.key) = \(translation ?? "FAILED")") + #expect(translation.isEmpty == false, "Translation for key '\(key)' in language '\(language)' is empty.") + print("Translation: \(key) [\(language)] = \(translation)") } } } @Test func intentsPlaceholders() { - let regex = #/\$\{([a-z0-9]*)\}/#.ignoresCase() - - guard let bundle = LocalizationTests.intentsBundle, - let path = bundle.url(forResource: "Intents", withExtension: "strings", subdirectory: nil, localization: "en"), - let placeholderTuples = (NSDictionary(contentsOf: path) as? [String: String])?.filter({ $0.value.contains("${") }), - !placeholderTuples.isEmpty + guard let stringCatalog = LocalizationTests.stringCatalog else { - Issue.record("Failed to load Intents.strings.") + Issue.record("Failed to load Localizable.xcstrings.") return } - let localizations = bundle.localizations.filter { $0 != "Base" } + let placeholderEntries = LocalizationTests.appIntentEntries.filter { key, entry in + LocalizationTests.sourceValue(for: key, entry: entry, sourceLanguage: stringCatalog.sourceLanguage).contains("${") + } - for language in localizations { - print("Testing language: '\(language)'.") + guard !placeholderEntries.isEmpty else { + Issue.record("Failed to load App Intents placeholder entries from Localizable.xcstrings.") + return + } - guard let path = bundle.url(forResource: "Intents", withExtension: "strings", subdirectory: nil, localization: language), - let languageTuples = (NSDictionary(contentsOf: path) as? [String: String])?.filter({ $0.value.contains("${") }), - !languageTuples.isEmpty - else { - Issue.record("Failed to load Intents.strings for language '\(language)'.") - continue - } + for (key, entry) in placeholderEntries { + let sourcePlaceholders = LocalizationTests.placeholders( + in: LocalizationTests.sourceValue(for: key, entry: entry, sourceLanguage: stringCatalog.sourceLanguage) + ) - #expect(placeholderTuples.count == languageTuples.count, "Number of strings with placeholders in language '\(language)' doesn't match. Translations to check: \(languageTuples.filter { !placeholderTuples.keys.contains($0.key) }).") + #expect(sourcePlaceholders.isEmpty == false, "Missing placeholders in source string for key '\(key)'.") - for placeholderTuple in placeholderTuples { - let placeholderString = placeholderTuple.value - guard let translation = placeholderTuple.key.localized(for: language, with: "Intents", in: bundle) else { + for language in LocalizationTests.localizations where language != stringCatalog.sourceLanguage { + guard let translation = LocalizationTests.translatedValue(for: language, entry: entry) else { continue } - let numberOfOccurrencesInPlaceholder = placeholderString.matches(of: regex).count - let numberOfOccurrencesInTranslation = translation.matches(of: regex).count - #expect(numberOfOccurrencesInPlaceholder == numberOfOccurrencesInTranslation, "Number of placeholders for key '\(placeholderTuple.key)' in language '\(language)' does not match.") - - let matchesPlaceholder = placeholderString.matches(of: regex).map { String($0.0) } - let matchesTranslation = translation.matches(of: regex).map { String($0.0) } - #expect(matchesPlaceholder.elementsEqual(matchesTranslation), "Placeholders do not match for key '\(placeholderTuple.key)' in language '\(language)'.") - print("Placeholders: \(matchesPlaceholder) == \(matchesTranslation)") + let translatedPlaceholders = LocalizationTests.placeholders(in: translation) + #expect( + sourcePlaceholders.elementsEqual(translatedPlaceholders), + "Placeholders do not match for key '\(key)' in language '\(language)'." + ) + print("Placeholders: \(sourcePlaceholders) == \(translatedPlaceholders)") } } } } private extension String { - func localized(for language: String, with table: String? = nil, in bundle: Bundle = .main) -> String? { - guard let path = bundle.path(forResource: language, ofType: "lproj") else { - return nil - } - - return Bundle(path: path)?.localizedString(forKey: self, value: "__MISSING__", table: table) + var nonEmpty: String? { + isEmpty ? nil : self } } diff --git a/openHABTestsSwift/Ressources/LocalizableTestCatalog.json b/openHABTestsSwift/Ressources/LocalizableTestCatalog.json new file mode 120000 index 000000000..55825fa32 --- /dev/null +++ b/openHABTestsSwift/Ressources/LocalizableTestCatalog.json @@ -0,0 +1 @@ +../../openHAB/Supporting Files/Localizable.xcstrings \ No newline at end of file diff --git a/openHABWatch/External/AppMessageService.swift b/openHABWatch/External/AppMessageService.swift index a48cce78c..295a01207 100644 --- a/openHABWatch/External/AppMessageService.swift +++ b/openHABWatch/External/AppMessageService.swift @@ -21,11 +21,21 @@ class AppMessageService: NSObject, WCSessionDelegate { private static let preferencesKey = "watchPreferences" + private func isExpectedApplicationContextRequestFailure(_ error: any Error) -> Bool { + let nsError = error as NSError + guard nsError.domain == WCErrorDomain else { return false } + + let normalizedDescription = nsError.localizedDescription.lowercased() + return normalizedDescription.contains("counterpart app not installed") + || normalizedDescription.contains("companion app not installed") + || normalizedDescription.contains("watch app not installed") + || normalizedDescription.contains("not paired") + } + @MainActor static func updateValuesFromApplicationContext(_ data: Data?) { guard let data else { - let key = preferencesKey - Logger.preferences.warning("⚠️ No \(key) data found in applicationContext.") + Logger.preferences.debug("No watch preferences received in applicationContext yet.") return } @@ -56,6 +66,10 @@ class AppMessageService: NSObject, WCSessionDelegate { AppMessageService.updateValuesFromApplicationContext(data) } } errorHandler: { error in + guard !self.isExpectedApplicationContextRequestFailure(error) else { + Logger.preferences.debug("Skipping application context request: \(error.localizedDescription)") + return + } Logger.preferences.error("Error sending message \(error.localizedDescription)") } }