From 636d643d90c9eb041781798d198d4c6c22584080 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 13 Oct 2026 17:28:18 +0200 Subject: [PATCH 1/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/NKCommon.swift | 6 +- Sources/NextcloudKit/NKError+Kind.swift | 204 ++++++++++++++++ Sources/NextcloudKit/NKError.swift | 103 +++++++- Sources/NextcloudKitUI/Localizable.xcstrings | 220 +++++++++++++++++ .../NKError+localizedMessage.swift | 76 ++++++ .../Views/Login/LoginFlowModel.swift | 22 +- .../Edit/UnifiedShareEditModel.swift | 12 +- .../Edit/UnifiedShareEditView.swift | 2 +- .../List/UnifiedShareListModel.swift | 3 +- .../List/UnifiedShareListView.swift | 2 +- .../NKErrorLocalizedMessageTests.swift | 42 ++++ .../NKErrorKindTests.swift | 221 ++++++++++++++++++ 12 files changed, 897 insertions(+), 16 deletions(-) create mode 100644 Sources/NextcloudKit/NKError+Kind.swift create mode 100644 Sources/NextcloudKitUI/NKError+localizedMessage.swift create mode 100644 Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift create mode 100644 Tests/NextcloudKitUnitTests/NKErrorKindTests.swift diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 89a6f31f..0976ba1c 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -162,7 +162,7 @@ public struct NKCommon: Sendable { @inline(__always) func checkCancelled() throws { if Task.isCancelled { - throw NKError(errorCode: -5, errorDescription: "Chunking was cancelled.") + throw NKError(errorCode: -5, errorDescription: "Chunking was cancelled.", kind: .cancelled) } } @@ -247,11 +247,11 @@ public struct NKCommon: Sendable { case -2: throw NKError(errorCode: -2, errorDescription: "Failed to open the output chunk file for writing.") case -3: - throw NKError(errorCode: -3, errorDescription: "There is not enough available disk space to proceed.") + throw NKError(errorCode: -3, errorDescription: "There is not enough available disk space to proceed.", kind: .localStorageFull) case -4: throw NKError(errorCode: -4, errorDescription: "Failed to write data to chunk file.") case -5: - throw NKError(errorCode: -5, errorDescription: "Chunking was cancelled.") + throw NKError(errorCode: -5, errorDescription: "Chunking was cancelled.", kind: .cancelled) default: continue } diff --git a/Sources/NextcloudKit/NKError+Kind.swift b/Sources/NextcloudKit/NKError+Kind.swift new file mode 100644 index 00000000..523904fc --- /dev/null +++ b/Sources/NextcloudKit/NKError+Kind.swift @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Alamofire +import Foundation + +public extension NKError { + /// What went wrong, independent of how the server or the system worded it. + /// + /// Choose the message shown to people from this, and keep ``NKError/errorDescription`` for logs. + enum Kind: Sendable, CaseIterable { + case offline + case timeout + case serverUnreachable + case secureConnection + case unauthorized + case forbidden + case notFound + /// `MKCOL` on an existing folder, or `COPY`/`MOVE` onto an existing item without overwriting it. + case alreadyExists + case conflict + case locked + case quotaExceeded + case tooLarge + case rateLimited + /// HTTP 503, which includes maintenance mode. + case unavailable + case serverError + case invalidResponse + case localStorageFull + case cancelled + case unknown + } +} + +public extension NKError.Kind { + /// Classifies an ``NKError/errorCode``: a URL loading error, an HTTP status or an OCS status code. + /// + /// Returns `nil` for `0`, which means success. The request method tells some WebDAV statuses apart. + init?(code: Int, method: String? = nil) { + switch code { + case 0: + return nil + + case ..<0: + self.init(urlErrorCode: code) + + case 996: + self = .serverError + + case 997: + self = .unauthorized + + case 998: + self = .notFound + + default: + self.init(httpStatusCode: code, method: method) + } + } + + /// Classifies any error, using its domain to tell codes apart. + init(error: Error) { + if let error = error as? NKError { + self = error.kind ?? .unknown + } else if error is CancellationError { + self = .cancelled + } else if error is DecodingError { + self = .invalidResponse + } else if let error = error as? AFError { + self.init(afError: error) + } else { + self.init(nsError: error as NSError) + } + } +} + +extension NKError.Kind { + init(urlErrorCode code: Int) { + switch code { + case NSURLErrorCancelled: + self = .cancelled + + case NSURLErrorNotConnectedToInternet, NSURLErrorDataNotAllowed, NSURLErrorInternationalRoamingOff, NSURLErrorCallIsActive: + self = .offline + + case NSURLErrorTimedOut: + self = .timeout + + case NSURLErrorCannotFindHost, NSURLErrorCannotConnectToHost, NSURLErrorNetworkConnectionLost, NSURLErrorDNSLookupFailed: + self = .serverUnreachable + + case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasUnknownRoot, + NSURLErrorServerCertificateNotYetValid, NSURLErrorClientCertificateRejected, NSURLErrorClientCertificateRequired, NSURLErrorAppTransportSecurityRequiresSecureConnection: + self = .secureConnection + + case NSURLErrorUserCancelledAuthentication, NSURLErrorUserAuthenticationRequired: + self = .unauthorized + + case NSURLErrorBadServerResponse, NSURLErrorZeroByteResource, NSURLErrorCannotDecodeRawData, NSURLErrorCannotDecodeContentData, + NSURLErrorCannotParseResponse, NSURLErrorHTTPTooManyRedirects, NSURLErrorRedirectToNonExistentLocation: + self = .invalidResponse + + case NSURLErrorResourceUnavailable: + self = .unavailable + + case NSURLErrorDataLengthExceedsMaximum: + self = .tooLarge + + default: + self = .unknown + } + } + + init(httpStatusCode: Int, method: String? = nil) { + let method = method?.uppercased() + + switch httpStatusCode { + case 401: + self = .unauthorized + + case 403: + self = .forbidden + + case 404, 410: + self = .notFound + + case 405 where method == "MKCOL": + self = .alreadyExists + + case 408, 504: + self = .timeout + + case 409: + self = .conflict + + case 412 where method == "COPY" || method == "MOVE": + self = .alreadyExists + + case 412: + self = .conflict + + case 413: + self = .tooLarge + + case 423: + self = .locked + + case 429: + self = .rateLimited + + case 502: + self = .serverUnreachable + + case 503: + self = .unavailable + + case 507: + self = .quotaExceeded + + case 500...511: + self = .serverError + + default: + self = .unknown + } + } + + private init(afError: AFError) { + if afError.isExplicitlyCancelledError { + self = .cancelled + } else if afError.isServerTrustEvaluationError { + self = .secureConnection + } else if let code = afError.responseCode { + self.init(httpStatusCode: code) + } else if afError.isResponseSerializationError { + self = .invalidResponse + } else if let underlyingError = afError.underlyingError { + self.init(error: underlyingError) + } else { + self = .unknown + } + } + + private init(nsError: NSError) { + switch (nsError.domain, nsError.code) { + case (NSURLErrorDomain, _): + self.init(urlErrorCode: nsError.code) + + case (NSCocoaErrorDomain, NSFileWriteOutOfSpaceError), (NSPOSIXErrorDomain, Int(ENOSPC)): + self = .localStorageFull + + case (NSCocoaErrorDomain, NSUserCancelledError): + self = .cancelled + + case (NSCocoaErrorDomain, NSPropertyListReadCorruptError): + self = .invalidResponse + + default: + self = .unknown + } + } +} diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index 7ccef8f7..6d92ee9a 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -30,7 +30,10 @@ extension OCSPath { static var ocsMetaCode: Self { ["ocs", "meta", "statuscode"] } static var ocsMetaMsg: Self { ["ocs", "meta", "message"] } static var ocsDataMsg: Self { ["ocs", "data", "message"] } + static var ocsData: Self { ["ocs", "data"] } static var ocsXMLMsg: Self { ["d:error", "s:message"] } + static var davException: Self { ["d:error", "s:exception"] } + static var jsonMsg: Self { ["message"] } } public struct NKError: Error, Equatable, Sendable { @@ -41,6 +44,17 @@ public struct NKError: Error, Equatable, Sendable { public let error: Error public let responseData: Data? + /// What went wrong, for choosing the message shown to people; `nil` for ``success``. + public let kind: Kind? + /// The HTTP status code of the response, when there was one. + public let httpStatusCode: Int? + /// The `ocs.meta.statuscode` of an OCS response. + public let ocsStatusCode: Int? + /// The text the server sent with the error: an OCS message, the WebDAV `s:message` or a JSON `message`. + public let serverMessage: String? + /// The WebDAV exception class from `s:exception`, e.g. `Sabre\DAV\Exception\Conflict`. + public let davException: String? + public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: NSLocalizedString("_invalid_url_", value: "Invalid server url", comment: "")) public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_error_response_", value: "Invalid response", comment: "")) public static let xmlError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_error_decode_xml_", value: "Invalid response, error decoding XML", comment: "")) @@ -55,10 +69,28 @@ public struct NKError: Error, Equatable, Sendable { public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: NSLocalizedString("_chunk_files_empty_", value: "Files not found", comment: "")) public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: NSLocalizedString("_error_create_folder_", value: "Create folder error", comment: "")) public static let errorChunkMoveFile = NKError(errorCode: -9997, errorDescription: NSLocalizedString("_error_move_folder_", value: "Move file error", comment: "")) - public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: NSLocalizedString("_no_enough_memory_", value: "No enough memory", comment: "")) + public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: NSLocalizedString("_no_enough_memory_", value: "No enough memory", comment: ""), kind: .localStorageFull) public static let success = NKError(errorCode: 0, errorDescription: "") + /// ``serverMessage`` when Nextcloud wrote it for people, otherwise `nil`. + /// + /// That covers OCS API errors with a 4xx status, which Nextcloud mostly translates (e.g. sharing and + /// password policy), and WebDAV file name rule violations. Other server texts are technical. + public var userFacingServerMessage: String? { + guard let serverMessage else { return nil } + + if let ocsStatusCode, 400..<500 ~= ocsStatusCode { + return serverMessage + } + + if davException == "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath" { + return serverMessage + } + + return nil + } + /// Returns a localized user-facing description for a known error code. /// /// - Parameter code: The HTTP, URL loading, WebDAV, OCS, or internal error code. @@ -186,13 +218,43 @@ public struct NKError: Error, Equatable, Sendable { return description } + /// The OCS status code, server message and WebDAV exception found in an error response body. + private static func details(of data: Data?) -> (ocsStatusCode: Int?, serverMessage: String?, davException: String?) { + guard let data, !data.isEmpty else { return (nil, nil, nil) } + + if let json = try? JSON(data: data) { + return (json[.ocsMetaCode].int, serverMessage(in: json), nil) + } + + let xml = XML.parse(data) + + return (xml[.ocsMetaCode].int, serverMessage(in: xml), firstNonEmpty(xml[.davException].text)) + } + + private static func serverMessage(in json: JSON) -> String? { + firstNonEmpty(json[.ocsDataMsg].string, json[.ocsData].string, json[.ocsMetaMsg].string, json[.jsonMsg].string) + } + + private static func serverMessage(in xml: XML.Accessor) -> String? { + firstNonEmpty(xml[.ocsDataMsg].text, xml[.ocsMetaMsg].text, xml[.ocsXMLMsg].text) + } + + private static func firstNonEmpty(_ candidates: String?...) -> String? { + candidates + .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } + .first { !$0.isEmpty } + } + /// Creates an `NKError` from an explicit code and description. /// /// - Parameters: /// - errorCode: The error code. /// - errorDescription: The user-facing error description. /// - responseData: Optional raw response data associated with the error. - public init(errorCode: Int = 0, errorDescription: String = "", responseData: Data? = nil) { + /// - kind: The kind of failure; derived from `errorCode` when `nil`. + public init(errorCode: Int = 0, errorDescription: String = "", responseData: Data? = nil, kind: Kind? = nil) { + let details = errorCode == 0 ? nil : NKError.details(of: responseData) + self.errorCode = errorCode self.errorDescription = errorDescription self.error = NSError( @@ -201,6 +263,11 @@ public struct NKError: Error, Equatable, Sendable { userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) self.responseData = responseData + self.kind = kind ?? Kind(code: errorCode) + self.httpStatusCode = nil + self.ocsStatusCode = details?.ocsStatusCode + self.serverMessage = details?.serverMessage + self.davException = details?.davException } /// Creates an `NKError` from a generic Swift `Error`. @@ -213,6 +280,11 @@ public struct NKError: Error, Equatable, Sendable { self.errorDescription = error.localizedDescription self.error = error self.responseData = responseData + self.kind = Kind(error: error) + self.httpStatusCode = (error as? AFError)?.responseCode + self.ocsStatusCode = nil + self.serverMessage = nil + self.davException = nil } /// Creates an `NKError` from an `NSError`. @@ -225,6 +297,11 @@ public struct NKError: Error, Equatable, Sendable { self.errorDescription = nsError.localizedDescription self.error = nsError self.responseData = responseData + self.kind = Kind(error: nsError) + self.httpStatusCode = nil + self.ocsStatusCode = nil + self.serverMessage = nil + self.davException = nil } /// Creates an `NKError` from an OCS JSON response. @@ -251,6 +328,11 @@ public struct NKError: Error, Equatable, Sendable { code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) + self.kind = errorCode == 0 ? nil : Kind(code: statuscode) + self.httpStatusCode = fallbackStatusCode + self.ocsStatusCode = rootJson[.ocsMetaCode].int + self.serverMessage = NKError.serverMessage(in: rootJson) + self.davException = nil } /// Creates an `NKError` from an HTTP status code. @@ -271,7 +353,14 @@ public struct NKError: Error, Equatable, Sendable { userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) + let details = NKError.details(of: responseData) + self.responseData = responseData + self.kind = Kind(code: statusCode) + self.httpStatusCode = statusCode + self.ocsStatusCode = details.ocsStatusCode + self.serverMessage = details.serverMessage + self.davException = details.davException } /// Creates an `NKError` from an HTTP response. @@ -289,8 +378,9 @@ public struct NKError: Error, Equatable, Sendable { /// - Parameters: /// - xmlData: The raw XML response data. /// - fallbackStatusCode: The fallback HTTP status code used when the OCS status code is missing. + /// - method: The HTTP method of the request, which tells some WebDAV statuses apart. /// - responseData: Optional raw response data associated with the error. - init(xmlData: Data, fallbackStatusCode: Int? = nil, responseData: Data? = nil) { + init(xmlData: Data, fallbackStatusCode: Int? = nil, method: String? = nil, responseData: Data? = nil) { let xml = XML.parse(xmlData) let statuscode = xml[.ocsMetaCode].int ?? fallbackStatusCode ?? NSURLErrorCannotDecodeContentData errorCode = 200..<300 ~= statuscode ? 0 : statuscode @@ -311,6 +401,11 @@ public struct NKError: Error, Equatable, Sendable { code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) + self.kind = errorCode == 0 ? nil : Kind(code: statuscode, method: method) + self.httpStatusCode = fallbackStatusCode + self.ocsStatusCode = xml[.ocsMetaCode].int + self.serverMessage = NKError.serverMessage(in: xml) + self.davException = NKError.firstNonEmpty(xml[.davException].text) } /// Creates an `NKError` from an Alamofire response and optional Alamofire error. @@ -335,7 +430,7 @@ public struct NKError: Error, Equatable, Sendable { if let errorJson = try? JSON(data: errorData) { self.init(rootJson: errorJson, fallbackStatusCode: errorCode, responseData: responseData) } else { - self.init(xmlData: errorData, fallbackStatusCode: errorCode, responseData: responseData) + self.init(xmlData: errorData, fallbackStatusCode: errorCode, method: dataResponse.request?.httpMethod, responseData: responseData) } } else if let error = error { diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index b45e7076..08f07259 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -67,6 +67,17 @@ } } }, + "A secure connection to the server couldn't be established." : { + "comment" : "Error message for TLS and certificate failures.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "A secure connection to the server couldn't be established." + } + } + } + }, "Accounts" : { "comment" : "Navigation title for the list of configured Nextcloud accounts.", "localizations" : { @@ -123,6 +134,17 @@ } } }, + "An item with this name already exists." : { + "comment" : "Error message when a file or folder with the same name already exists.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "An item with this name already exists." + } + } + } + }, "Apply permissions to all recipients?" : { "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", "localizations" : { @@ -156,6 +178,17 @@ } } }, + "Can't reach the server. Check the address or try again later." : { + "comment" : "Error message when the server cannot be reached.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Can't reach the server. Check the address or try again later." + } + } + } + }, "Cancel" : { "comment" : "Button that dismisses a confirmation dialog without making changes.", "localizations" : { @@ -393,6 +426,17 @@ } } }, + "No Nextcloud server was found at this address." : { + "comment" : "Login error message when the entered address does not point to a Nextcloud server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Nextcloud server was found at this address." + } + } + } + }, "No recipients" : { "comment" : "Recipient summary when a share has no recipients.", "localizations" : { @@ -620,6 +664,138 @@ } } }, + "Something went wrong. Try again." : { + "comment" : "Error message when the cause of the error is unknown.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Something went wrong. Try again." + } + } + } + }, + "The file is locked. Try again later." : { + "comment" : "Error message when the file is locked on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The file is locked. Try again later." + } + } + } + }, + "The file is too large for the server." : { + "comment" : "Error message when the server rejects a file because of its size.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The file is too large for the server." + } + } + } + }, + "The item could not be found on the server." : { + "comment" : "Error message when the requested file, folder or share does not exist on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The item could not be found on the server." + } + } + } + }, + "The item was changed on the server. Refresh and try again." : { + "comment" : "Error message when the request conflicts with the current state on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The item was changed on the server. Refresh and try again." + } + } + } + }, + "The request was cancelled." : { + "comment" : "Error message when a request was cancelled.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The request was cancelled." + } + } + } + }, + "The server is temporarily unavailable. Try again later." : { + "comment" : "Error message when the server is unavailable, e.g. in maintenance mode.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server is temporarily unavailable. Try again later." + } + } + } + }, + "The server ran into a problem. Try again later." : { + "comment" : "Error message for internal server errors.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server ran into a problem. Try again later." + } + } + } + }, + "The server sent an unexpected response. Try again later." : { + "comment" : "Error message when the server's response cannot be read.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server sent an unexpected response. Try again later." + } + } + } + }, + "The server took too long to respond. Try again later." : { + "comment" : "Error message when a request to the server timed out.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server took too long to respond. Try again later." + } + } + } + }, + "There is not enough storage space left on the server." : { + "comment" : "Error message when the account's storage quota is used up.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is not enough storage space left on the server." + } + } + } + }, + "There is not enough storage space left on this device." : { + "comment" : "Error message when the device runs out of storage space.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is not enough storage space left on this device." + } + } + } + }, "This change will replace the individual permission settings of all recipients in this share." : { "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", "localizations" : { @@ -708,6 +884,50 @@ } } } + }, + "Too many requests. Wait a moment and try again." : { + "comment" : "Error message when the server rate-limits the requests.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Too many requests. Wait a moment and try again." + } + } + } + }, + "You don't have permission to do this." : { + "comment" : "Error message when the server forbids the action.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You don't have permission to do this." + } + } + } + }, + "You're offline. Check your internet connection." : { + "comment" : "Error message when the device has no internet connection.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You're offline. Check your internet connection." + } + } + } + }, + "Your login is no longer valid. Log in again." : { + "comment" : "Error message when the server rejects the account's credentials.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your login is no longer valid. Log in again." + } + } + } } }, "version" : "1.0" diff --git a/Sources/NextcloudKitUI/NKError+localizedMessage.swift b/Sources/NextcloudKitUI/NKError+localizedMessage.swift new file mode 100644 index 00000000..cea79651 --- /dev/null +++ b/Sources/NextcloudKitUI/NKError+localizedMessage.swift @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit + +extension NKError { + /// + /// Text for showing this error to people: the server's own message when Nextcloud wrote it for them, otherwise one that fits `kind`. + /// + var localizedMessage: String { + if let userFacingServerMessage { + return userFacingServerMessage + } + + switch kind ?? .unknown { + case .offline: + return String(localized: "You're offline. Check your internet connection.", bundle: .module, comment: "Error message when the device has no internet connection.") + + case .timeout: + return String(localized: "The server took too long to respond. Try again later.", bundle: .module, comment: "Error message when a request to the server timed out.") + + case .serverUnreachable: + return String(localized: "Can't reach the server. Check the address or try again later.", bundle: .module, comment: "Error message when the server cannot be reached.") + + case .secureConnection: + return String(localized: "A secure connection to the server couldn't be established.", bundle: .module, comment: "Error message for TLS and certificate failures.") + + case .unauthorized: + return String(localized: "Your login is no longer valid. Log in again.", bundle: .module, comment: "Error message when the server rejects the account's credentials.") + + case .forbidden: + return String(localized: "You don't have permission to do this.", bundle: .module, comment: "Error message when the server forbids the action.") + + case .notFound: + return String(localized: "The item could not be found on the server.", bundle: .module, comment: "Error message when the requested file, folder or share does not exist on the server.") + + case .alreadyExists: + return String(localized: "An item with this name already exists.", bundle: .module, comment: "Error message when a file or folder with the same name already exists.") + + case .conflict: + return String(localized: "The item was changed on the server. Refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") + + case .locked: + return String(localized: "The file is locked. Try again later.", bundle: .module, comment: "Error message when the file is locked on the server.") + + case .quotaExceeded: + return String(localized: "There is not enough storage space left on the server.", bundle: .module, comment: "Error message when the account's storage quota is used up.") + + case .tooLarge: + return String(localized: "The file is too large for the server.", bundle: .module, comment: "Error message when the server rejects a file because of its size.") + + case .rateLimited: + return String(localized: "Too many requests. Wait a moment and try again.", bundle: .module, comment: "Error message when the server rate-limits the requests.") + + case .unavailable: + return String(localized: "The server is temporarily unavailable. Try again later.", bundle: .module, comment: "Error message when the server is unavailable, e.g. in maintenance mode.") + + case .serverError: + return String(localized: "The server ran into a problem. Try again later.", bundle: .module, comment: "Error message for internal server errors.") + + case .invalidResponse: + return String(localized: "The server sent an unexpected response. Try again later.", bundle: .module, comment: "Error message when the server's response cannot be read.") + + case .localStorageFull: + return String(localized: "There is not enough storage space left on this device.", bundle: .module, comment: "Error message when the device runs out of storage space.") + + case .cancelled: + return String(localized: "The request was cancelled.", bundle: .module, comment: "Error message when a request was cancelled.") + + case .unknown: + return String(localized: "Something went wrong. Try again.", bundle: .module, comment: "Error message when the cause of the error is unknown.") + } + } +} diff --git a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift index ab016948..a57662a8 100644 --- a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift +++ b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift @@ -145,9 +145,24 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing { cancel() if let error { - let message = (error as? NKError)?.errorDescription ?? error.localizedDescription - present(error: message) + let nkError = error as? NKError ?? NKError(error: error) + + nkLog(error: "Login failed: \(nkError.errorCode) \(nkError.errorDescription)") + present(error: message(for: nkError)) + } + } + + /// + /// A 404 or an unreadable response during login means there is no Nextcloud at the entered address. + /// + private func message(for error: NKError) -> String { + let isNotNextcloud = error.kind == .notFound || error.kind == .invalidResponse + + if isNotNextcloud, error.userFacingServerMessage == nil { + return String(localized: "No Nextcloud server was found at this address.", bundle: .module, comment: "Login error message when the entered address does not point to a Nextcloud server.") } + + return error.localizedMessage } private func present(error: String?) { @@ -194,7 +209,8 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing { if result.error == .success, let token = result.token { addAccount(credentials.host, credentials.user, token) } else { - present(error: result.error.errorDescription) + nkLog(error: "One-time login failed: \(result.error.errorCode) \(result.error.errorDescription)") + present(error: message(for: result.error)) } } } diff --git a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift index cd34829a..f68d8f11 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift @@ -31,7 +31,13 @@ public class UnifiedShareEditModel { /// Last property-update error text, keyed by property class (shown under the field). var propertyErrors: [String: String] = [:] /// Transient mutation failure, shown as an alert while the form stays usable. - var mutationError: NKError? + var mutationError: NKError? { + didSet { + if let mutationError { + nkLog(error: "Could not update share: \(mutationError.errorCode) \(mutationError.errorDescription)") + } + } + } /// Changes on a failed permission mutation so controls are recreated from server values. var permissionResetRevision = 0 /// Per-property revisions used to restore editors after a rejected mutation. @@ -451,8 +457,8 @@ public class UnifiedShareEditModel { pendingProperties.remove(propertyClass) guard let share = result.share else { - let description = result.error.errorDescription - propertyErrors[propertyClass] = description.isEmpty ? String(localized: "Failed to update share.", bundle: .module) : description + nkLog(error: "Could not update share property \(propertyClass): \(result.error.errorCode) \(result.error.errorDescription)") + propertyErrors[propertyClass] = result.error.localizedMessage propertyResetRevisions[propertyClass, default: 0] += 1 return diff --git a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift index ec4a4792..d030c939 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift @@ -226,7 +226,7 @@ public struct UnifiedShareEditView: View { Button(String(localized: "OK", bundle: .module), role: .cancel) {} } message: { if let error = model.mutationError { - Text(error.errorDescription) + Text(error.localizedMessage) } } .confirmationDialog( diff --git a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListModel.swift b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListModel.swift index 79984e6f..4cb00822 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListModel.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListModel.swift @@ -9,7 +9,7 @@ import NextcloudKit enum UnifiedShareListState { case loading case loaded([NKUnifiedShare]) - case error(Error) + case error(NKError) } @MainActor @@ -67,6 +67,7 @@ public class UnifiedShareListModel { if case .loaded = state { onError?(result.error) } else { + nkLog(error: "Could not load shares: \(result.error.errorCode) \(result.error.errorDescription)") state = .error(result.error) } diff --git a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift index 8e91ba91..21d0da28 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift @@ -98,7 +98,7 @@ public struct UnifiedShareListView: View { ContentUnavailableView { Label(String(localized: "Couldn't load shares", bundle: .module), systemImage: "exclamationmark.triangle") } description: { - Text(error.localizedDescription) + Text(error.localizedMessage) } actions: { Button(String(localized: "Retry", bundle: .module)) { model.load() diff --git a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift new file mode 100644 index 00000000..776f1998 --- /dev/null +++ b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit +@testable import NextcloudKitUI +import Testing + +/// +/// Tests for the message ``NKError/localizedMessage`` shows to people. +/// +struct NKErrorLocalizedMessageTests { + @Test(arguments: NKError.Kind.allCases) + func kindMessage(kind: NKError.Kind) { + let message = NKError(errorCode: -1, errorDescription: "Technical description", kind: kind).localizedMessage + + #expect(!message.isEmpty) + #expect(message != "Technical description") + } + + @Test func distinctMessagePerKind() { + let messages = Set(NKError.Kind.allCases.map { NKError(errorCode: -1, kind: $0).localizedMessage }) + + #expect(messages.count == NKError.Kind.allCases.count) + } + + @Test func prefersUserFacingServerMessage() { + let body = Data(#"{"ocs":{"meta":{"statuscode":400},"data":"Password needs to be at least 10 characters long"}}"#.utf8) + let error = NKError(errorCode: 400, errorDescription: "400: Bad request", responseData: body) + + #expect(error.localizedMessage == "Password needs to be at least 10 characters long") + } + + @Test func ignoresTechnicalServerMessage() { + let body = Data(#"Sabre\DAV\Exception\ConflictParent node does not exist"#.utf8) + let error = NKError(errorCode: 409, errorDescription: "Parent node does not exist", responseData: body) + + #expect(error.localizedMessage != "Parent node does not exist") + #expect(error.localizedMessage == NKError(errorCode: 409).localizedMessage) + } +} diff --git a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift new file mode 100644 index 00000000..50ca8c68 --- /dev/null +++ b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift @@ -0,0 +1,221 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Alamofire +import Foundation +import SwiftyJSON +import Testing +@testable import NextcloudKit + +/// Verifies that `NKError` classifies failures into a `Kind` and keeps the raw server details apart. +@Suite("NKError kind and server details") +struct NKErrorKindTests { + static let urlErrorCases: [(Int, NKError.Kind)] = [ + (NSURLErrorNotConnectedToInternet, .offline), + (NSURLErrorTimedOut, .timeout), + (NSURLErrorCannotFindHost, .serverUnreachable), + (NSURLErrorServerCertificateUntrusted, .secureConnection), + (NSURLErrorUserAuthenticationRequired, .unauthorized), + (NSURLErrorCannotParseResponse, .invalidResponse), + (NSURLErrorCancelled, .cancelled), + (NSURLErrorUnknown, .unknown) + ] + + static let httpStatusCases: [(Int, NKError.Kind)] = [ + (400, .unknown), + (401, .unauthorized), + (403, .forbidden), + (404, .notFound), + (409, .conflict), + (412, .conflict), + (413, .tooLarge), + (423, .locked), + (429, .rateLimited), + (500, .serverError), + (502, .serverUnreachable), + (503, .unavailable), + (504, .timeout), + (507, .quotaExceeded) + ] + + private let davURL = URL(string: "https://cloud.example.com/remote.php/dav/files/alice/Documents")! + + private func davErrorBody(exception: String, message: String) -> Data { + Data(""" + + + \(exception) + \(message) + + """.utf8) + } + + private func responseError(statusCode: Int, method: String, body: Data?) -> NKError { + var request = URLRequest(url: davURL) + request.httpMethod = method + + let response = HTTPURLResponse(url: davURL, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil) + let dataResponse = AFDataResponse(request: request, response: response, data: body, metrics: nil, serializationDuration: 0, result: .failure(.responseValidationFailed(reason: .unacceptableStatusCode(code: statusCode)))) + + return NKError(error: dataResponse.error, afResponse: dataResponse, responseData: body) + } + + @Test("Classifies URL loading errors", arguments: NKErrorKindTests.urlErrorCases) + func urlErrors(code: Int, kind: NKError.Kind) { + let error = NKError(nsError: NSError(domain: NSURLErrorDomain, code: code)) + + #expect(error.kind == kind) + #expect(error.httpStatusCode == nil) + } + + @Test("Classifies HTTP statuses without changing the description", arguments: NKErrorKindTests.httpStatusCases) + func httpStatuses(statusCode: Int, kind: NKError.Kind) { + let error = NKError(statusCode: statusCode, fallbackDescription: "Fallback") + + #expect(error.kind == kind) + #expect(error.httpStatusCode == statusCode) + #expect(error.errorDescription.hasPrefix("\(statusCode): ")) + } + + @Test("Uses the request method to tell WebDAV statuses apart") + func requestMethod() { + let body = davErrorBody(exception: "Sabre\\DAV\\Exception\\MethodNotAllowed", message: "The resource you tried to create already exists") + + #expect(responseError(statusCode: 405, method: "MKCOL", body: body).kind == .alreadyExists) + #expect(responseError(statusCode: 405, method: "PROPFIND", body: body).kind == .unknown) + #expect(responseError(statusCode: 412, method: "MOVE", body: body).kind == .alreadyExists) + #expect(responseError(statusCode: 412, method: "COPY", body: body).kind == .alreadyExists) + #expect(responseError(statusCode: 412, method: "PUT", body: body).kind == .conflict) + } + + @Test("Reads the WebDAV exception and message, which stay out of the UI") + func davDetails() { + let error = responseError(statusCode: 409, method: "MKCOL", body: davErrorBody(exception: "Sabre\\DAV\\Exception\\Conflict", message: "Parent node does not exist")) + + #expect(error.kind == .conflict) + #expect(error.httpStatusCode == 409) + #expect(error.ocsStatusCode == nil) + #expect(error.davException == "Sabre\\DAV\\Exception\\Conflict") + #expect(error.serverMessage == "Parent node does not exist") + #expect(error.userFacingServerMessage == nil) + #expect(error.errorDescription == "Parent node does not exist") + } + + @Test("Treats WebDAV file name rule violations as user-facing") + func invalidPathIsUserFacing() { + let message = "\"/\" is not allowed inside a file or folder name." + let error = responseError(statusCode: 400, method: "MKCOL", body: davErrorBody(exception: "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath", message: message)) + + #expect(error.kind == .unknown) + #expect(error.userFacingServerMessage == message) + } + + @Test("Reads the OCS status code and message") + func ocsDetails() throws { + let json = try JSON(data: Data(#"{"ocs":{"meta":{"status":"failure","statuscode":403,"message":"Group sharing is disabled by the administrator"},"data":[]}}"#.utf8)) + let error = NKError(rootJson: json, fallbackStatusCode: 403) + + #expect(error.kind == .forbidden) + #expect(error.httpStatusCode == 403) + #expect(error.ocsStatusCode == 403) + #expect(error.serverMessage == "Group sharing is disabled by the administrator") + #expect(error.userFacingServerMessage == "Group sharing is disabled by the administrator") + #expect(error.errorDescription == "Group sharing is disabled by the administrator") + } + + @Test("Takes the message from a string `ocs.data`, as the unified sharing API sends it") + func ocsDataMessage() { + let body = Data(#"{"ocs":{"meta":{"statuscode":400,"message":""},"data":"Password needs to be at least 10 characters long"}}"#.utf8) + let error = responseError(statusCode: 400, method: "POST", body: body) + + #expect(error.ocsStatusCode == 400) + #expect(error.userFacingServerMessage == "Password needs to be at least 10 characters long") + } + + @Test("Keeps OCS server errors out of the UI") + func ocsServerError() throws { + let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":500,"message":"Internal error"},"data":[]}}"#.utf8)) + let error = NKError(rootJson: json, fallbackStatusCode: 500) + + #expect(error.kind == .serverError) + #expect(error.serverMessage == "Internal error") + #expect(error.userFacingServerMessage == nil) + } + + @Test("Maps OCS v1 status codes") + func ocsV1StatusCodes() throws { + let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":997,"message":"Current user is not logged in"},"data":[]}}"#.utf8)) + let error = NKError(rootJson: json, fallbackStatusCode: 200) + + #expect(error.kind == .unauthorized) + #expect(error.userFacingServerMessage == nil) + } + + @Test("A successful OCS response has no kind") + func ocsSuccess() throws { + let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":200,"message":"OK"},"data":[]}}"#.utf8)) + let error = NKError(rootJson: json, fallbackStatusCode: 200) + + #expect(error.errorCode == 0) + #expect(error.kind == nil) + } + + @Test("Reads server details from the response data of explicit errors") + func explicitErrorWithResponseData() { + let body = Data(#"{"ocs":{"meta":{"statuscode":404},"data":"Share not found"}}"#.utf8) + let error = NKError(errorCode: 404, errorDescription: "Share not found", responseData: body) + + #expect(error.kind == .notFound) + #expect(error.ocsStatusCode == 404) + #expect(error.userFacingServerMessage == "Share not found") + } + + @Test("Derives the kind of explicit and preset errors") + func explicitErrors() { + #expect(NKError.success.kind == nil) + #expect(NKError.cancelled.kind == .cancelled) + #expect(NKError.unauthorizedError.kind == .unauthorized) + #expect(NKError.forbiddenError.kind == .forbidden) + #expect(NKError.unavailableError.kind == .unavailable) + #expect(NKError.invalidData.kind == .invalidResponse) + #expect(NKError.xmlError.kind == .invalidResponse) + #expect(NKError.errorChunkNoEnoughMemory.kind == .localStorageFull) + #expect(NKError(errorCode: -99999, errorDescription: "_internal_error_").kind == .unknown) + #expect(NKError(errorCode: -3, errorDescription: "No space", kind: .localStorageFull).kind == .localStorageFull) + } + + @Test("Classifies errors that do not come from a response") + func otherErrors() { + let decodingError = DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Broken")) + + #expect(NKError(error: decodingError).kind == .invalidResponse) + #expect(NKError(error: CancellationError()).kind == .cancelled) + #expect(NKError(error: AFError.explicitlyCancelled).kind == .cancelled) + #expect(NKError(nsError: NSError(domain: NSCocoaErrorDomain, code: NSFileWriteOutOfSpaceError)).kind == .localStorageFull) + #expect(NKError(error: NKError.forbiddenError).kind == .forbidden) + } + + @Test("Classifies transport failures without a response") + func transportFailure() { + let dataResponse = AFDataResponse(request: URLRequest(url: davURL), response: nil, data: nil, metrics: nil, serializationDuration: 0, result: .failure(.sessionTaskFailed(error: URLError(.notConnectedToInternet)))) + let error = NKError(error: dataResponse.error, afResponse: dataResponse) + + #expect(error.kind == .offline) + #expect(error.errorCode == NSURLErrorNotConnectedToInternet) + } + + @Test("Classifies stored error codes") + func storedCodes() { + #expect(NKError.Kind(code: 0) == nil) + #expect(NKError.Kind(code: 507) == .quotaExceeded) + #expect(NKError.Kind(code: NSURLErrorNotConnectedToInternet) == .offline) + #expect(NKError.Kind(code: 997) == .unauthorized) + #expect(NKError.Kind(code: 405, method: "MKCOL") == .alreadyExists) + } + + @Test("Equality still compares only the code and the description") + func equality() { + #expect(NKError(errorCode: 404, errorDescription: "Gone", kind: .notFound) == NKError(errorCode: 404, errorDescription: "Gone", kind: .unknown)) + } +} From 84ff1e54aee8c217e547228dab13453ab264af0e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 15 Oct 2026 11:02:39 +0200 Subject: [PATCH 2/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKitUI/Localizable.xcstrings | 22 +++++++++---------- .../Views/Login/LoginFlowModel.swift | 8 +++---- .../NKErrorLocalizedMessageTests.swift | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index 06cca038..557d0064 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -6906,17 +6906,6 @@ } } }, - "No Nextcloud server was found at this address." : { - "comment" : "Login error message when the entered address does not point to a Nextcloud server.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Nextcloud server was found at this address." - } - } - } - }, "No recipients" : { "comment" : "Recipient summary when a share has no recipients.", "localizations" : { @@ -7012,6 +7001,17 @@ } } }, + "No server was found at this address." : { + "comment" : "Login error message when the entered address does not point to a Nextcloud server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No server was found at this address." + } + } + } + }, "Not Shared Yet" : { "comment" : "Empty-state title shown when a file or folder has no unified shares.", "localizations" : { diff --git a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift index a57662a8..94c85937 100644 --- a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift +++ b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift @@ -153,13 +153,13 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing { } /// - /// A 404 or an unreadable response during login means there is no Nextcloud at the entered address. + /// A 404 or an unreadable response during login means there is no Nextcloud instance at the entered address. /// private func message(for error: NKError) -> String { - let isNotNextcloud = error.kind == .notFound || error.kind == .invalidResponse + let isInstanceNotFound = error.kind == .notFound || error.kind == .invalidResponse - if isNotNextcloud, error.userFacingServerMessage == nil { - return String(localized: "No Nextcloud server was found at this address.", bundle: .module, comment: "Login error message when the entered address does not point to a Nextcloud server.") + if isInstanceNotFound, error.userFacingServerMessage == nil { + return String(localized: "No server was found at this address.", bundle: .module, comment: "Login error message when the entered address does not point to a Nextcloud server.") } return error.localizedMessage diff --git a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift index 776f1998..fe0dac8b 100644 --- a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift +++ b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift @@ -8,7 +8,7 @@ import NextcloudKit import Testing /// -/// Tests for the message ``NKError/localizedMessage`` shows to people. +/// Tests for the message ``NKError/localizedMessage`` shows to users. /// struct NKErrorLocalizedMessageTests { @Test(arguments: NKError.Kind.allCases) From ab0c049b2d39fc0dc964ac0743f68c0d0f4002e3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 15 Oct 2026 11:16:21 +0200 Subject: [PATCH 3/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/NKError.swift | 2 +- Sources/NextcloudKitUI/Localizable.xcstrings | 16 ++++++++-------- ...ift => NKError+localizedGenericMessage.swift} | 12 ++++++------ .../Views/Login/LoginFlowModel.swift | 2 +- .../Edit/UnifiedShareEditModel.swift | 2 +- .../Edit/UnifiedShareEditView.swift | 2 +- .../List/UnifiedShareListView.swift | 2 +- .../NKErrorLocalizedMessageTests.swift | 12 ++++++------ 8 files changed, 25 insertions(+), 25 deletions(-) rename Sources/NextcloudKitUI/{NKError+localizedMessage.swift => NKError+localizedGenericMessage.swift} (82%) diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index 6d92ee9a..b8a4f0d6 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -73,7 +73,7 @@ public struct NKError: Error, Equatable, Sendable { public static let success = NKError(errorCode: 0, errorDescription: "") - /// ``serverMessage`` when Nextcloud wrote it for people, otherwise `nil`. + /// ``serverMessage`` when the server messages is user friendly, otherwise `nil`. /// /// That covers OCS API errors with a 4xx status, which Nextcloud mostly translates (e.g. sharing and /// password policy), and WebDAV file name rule violations. Other server texts are technical. diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index 557d0064..f6a2b71a 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -12010,13 +12010,13 @@ } } }, - "Something went wrong. Try again." : { + "Something went wrong. Please try again." : { "comment" : "Error message when the cause of the error is unknown.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Something went wrong. Try again." + "value" : "Something went wrong. Please try again." } } } @@ -12054,13 +12054,13 @@ } } }, - "The item was changed on the server. Refresh and try again." : { + "The item was changed on the server. Please refresh and try again." : { "comment" : "Error message when the request conflicts with the current state on the server.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The item was changed on the server. Refresh and try again." + "value" : "The item was changed on the server. Please refresh and try again." } } } @@ -12807,13 +12807,13 @@ } } }, - "Too many requests. Wait a moment and try again." : { + "Too many requests. Please wait a moment and try again." : { "comment" : "Error message when the server rate-limits the requests.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Too many requests. Wait a moment and try again." + "value" : "Too many requests. Please wait a moment and try again." } } } @@ -12840,13 +12840,13 @@ } } }, - "Your login is no longer valid. Log in again." : { + "Your login is no longer valid. Please log in again." : { "comment" : "Error message when the server rejects the account's credentials.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Your login is no longer valid. Log in again." + "value" : "Your login is no longer valid. Please log in again." } } } diff --git a/Sources/NextcloudKitUI/NKError+localizedMessage.swift b/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift similarity index 82% rename from Sources/NextcloudKitUI/NKError+localizedMessage.swift rename to Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift index cea79651..95d1cc13 100644 --- a/Sources/NextcloudKitUI/NKError+localizedMessage.swift +++ b/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift @@ -7,9 +7,9 @@ import NextcloudKit extension NKError { /// - /// Text for showing this error to people: the server's own message when Nextcloud wrote it for them, otherwise one that fits `kind`. + /// User friendly messages for generic errors. Each one fits a`kind`. /// - var localizedMessage: String { + var localizedGenericMessage: String { if let userFacingServerMessage { return userFacingServerMessage } @@ -28,7 +28,7 @@ extension NKError { return String(localized: "A secure connection to the server couldn't be established.", bundle: .module, comment: "Error message for TLS and certificate failures.") case .unauthorized: - return String(localized: "Your login is no longer valid. Log in again.", bundle: .module, comment: "Error message when the server rejects the account's credentials.") + return String(localized: "Your login is no longer valid. Please log in again.", bundle: .module, comment: "Error message when the server rejects the account's credentials.") case .forbidden: return String(localized: "You don't have permission to do this.", bundle: .module, comment: "Error message when the server forbids the action.") @@ -40,7 +40,7 @@ extension NKError { return String(localized: "An item with this name already exists.", bundle: .module, comment: "Error message when a file or folder with the same name already exists.") case .conflict: - return String(localized: "The item was changed on the server. Refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") + return String(localized: "The item was changed on the server. Please refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") case .locked: return String(localized: "The file is locked. Try again later.", bundle: .module, comment: "Error message when the file is locked on the server.") @@ -52,7 +52,7 @@ extension NKError { return String(localized: "The file is too large for the server.", bundle: .module, comment: "Error message when the server rejects a file because of its size.") case .rateLimited: - return String(localized: "Too many requests. Wait a moment and try again.", bundle: .module, comment: "Error message when the server rate-limits the requests.") + return String(localized: "Too many requests. Please wait a moment and try again.", bundle: .module, comment: "Error message when the server rate-limits the requests.") case .unavailable: return String(localized: "The server is temporarily unavailable. Try again later.", bundle: .module, comment: "Error message when the server is unavailable, e.g. in maintenance mode.") @@ -70,7 +70,7 @@ extension NKError { return String(localized: "The request was cancelled.", bundle: .module, comment: "Error message when a request was cancelled.") case .unknown: - return String(localized: "Something went wrong. Try again.", bundle: .module, comment: "Error message when the cause of the error is unknown.") + return String(localized: "Something went wrong. Please try again.", bundle: .module, comment: "Error message when the cause of the error is unknown.") } } } diff --git a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift index 94c85937..a311be03 100644 --- a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift +++ b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift @@ -162,7 +162,7 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing { return String(localized: "No server was found at this address.", bundle: .module, comment: "Login error message when the entered address does not point to a Nextcloud server.") } - return error.localizedMessage + return error.localizedGenericMessage } private func present(error: String?) { diff --git a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift index f68d8f11..654d399d 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditModel.swift @@ -458,7 +458,7 @@ public class UnifiedShareEditModel { guard let share = result.share else { nkLog(error: "Could not update share property \(propertyClass): \(result.error.errorCode) \(result.error.errorDescription)") - propertyErrors[propertyClass] = result.error.localizedMessage + propertyErrors[propertyClass] = result.error.localizedGenericMessage propertyResetRevisions[propertyClass, default: 0] += 1 return diff --git a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift index d030c939..b5fbca09 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/Edit/UnifiedShareEditView.swift @@ -226,7 +226,7 @@ public struct UnifiedShareEditView: View { Button(String(localized: "OK", bundle: .module), role: .cancel) {} } message: { if let error = model.mutationError { - Text(error.localizedMessage) + Text(error.localizedGenericMessage) } } .confirmationDialog( diff --git a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift index 21d0da28..0e56a34a 100644 --- a/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift +++ b/Sources/NextcloudKitUI/Views/Unified Share/List/UnifiedShareListView.swift @@ -98,7 +98,7 @@ public struct UnifiedShareListView: View { ContentUnavailableView { Label(String(localized: "Couldn't load shares", bundle: .module), systemImage: "exclamationmark.triangle") } description: { - Text(error.localizedMessage) + Text(error.localizedGenericMessage) } actions: { Button(String(localized: "Retry", bundle: .module)) { model.load() diff --git a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift index fe0dac8b..2f0a47a7 100644 --- a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift +++ b/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift @@ -8,19 +8,19 @@ import NextcloudKit import Testing /// -/// Tests for the message ``NKError/localizedMessage`` shows to users. +/// Tests for the message ``NKError/localizedGenericMessage`` shows to users. /// struct NKErrorLocalizedMessageTests { @Test(arguments: NKError.Kind.allCases) func kindMessage(kind: NKError.Kind) { - let message = NKError(errorCode: -1, errorDescription: "Technical description", kind: kind).localizedMessage + let message = NKError(errorCode: -1, errorDescription: "Technical description", kind: kind).localizedGenericMessage #expect(!message.isEmpty) #expect(message != "Technical description") } @Test func distinctMessagePerKind() { - let messages = Set(NKError.Kind.allCases.map { NKError(errorCode: -1, kind: $0).localizedMessage }) + let messages = Set(NKError.Kind.allCases.map { NKError(errorCode: -1, kind: $0).localizedGenericMessage }) #expect(messages.count == NKError.Kind.allCases.count) } @@ -29,14 +29,14 @@ struct NKErrorLocalizedMessageTests { let body = Data(#"{"ocs":{"meta":{"statuscode":400},"data":"Password needs to be at least 10 characters long"}}"#.utf8) let error = NKError(errorCode: 400, errorDescription: "400: Bad request", responseData: body) - #expect(error.localizedMessage == "Password needs to be at least 10 characters long") + #expect(error.localizedGenericMessage == "Password needs to be at least 10 characters long") } @Test func ignoresTechnicalServerMessage() { let body = Data(#"Sabre\DAV\Exception\ConflictParent node does not exist"#.utf8) let error = NKError(errorCode: 409, errorDescription: "Parent node does not exist", responseData: body) - #expect(error.localizedMessage != "Parent node does not exist") - #expect(error.localizedMessage == NKError(errorCode: 409).localizedMessage) + #expect(error.localizedGenericMessage != "Parent node does not exist") + #expect(error.localizedGenericMessage == NKError(errorCode: 409).localizedGenericMessage) } } From c1dfe63c65d869a864292b0b02330ef9f64c9cae Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 15 Oct 2026 12:45:34 +0200 Subject: [PATCH 4/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/NKError+Kind.swift | 2 +- Sources/NextcloudKit/NKError.swift | 10 +++++----- Sources/NextcloudKitUI/Localizable.xcstrings | 4 ++-- .../NKError+localizedGenericMessage.swift | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/NextcloudKit/NKError+Kind.swift b/Sources/NextcloudKit/NKError+Kind.swift index 523904fc..1d41ffbb 100644 --- a/Sources/NextcloudKit/NKError+Kind.swift +++ b/Sources/NextcloudKit/NKError+Kind.swift @@ -34,7 +34,7 @@ public extension NKError { } } -public extension NKError.Kind { +extension NKError.Kind { /// Classifies an ``NKError/errorCode``: a URL loading error, an HTTP status or an OCS status code. /// /// Returns `nil` for `0`, which means success. The request method tells some WebDAV statuses apart. diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index b8a4f0d6..616ea79f 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -47,13 +47,13 @@ public struct NKError: Error, Equatable, Sendable { /// What went wrong, for choosing the message shown to people; `nil` for ``success``. public let kind: Kind? /// The HTTP status code of the response, when there was one. - public let httpStatusCode: Int? + let httpStatusCode: Int? /// The `ocs.meta.statuscode` of an OCS response. - public let ocsStatusCode: Int? + let ocsStatusCode: Int? /// The text the server sent with the error: an OCS message, the WebDAV `s:message` or a JSON `message`. - public let serverMessage: String? + let serverMessage: String? /// The WebDAV exception class from `s:exception`, e.g. `Sabre\DAV\Exception\Conflict`. - public let davException: String? + let davException: String? public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: NSLocalizedString("_invalid_url_", value: "Invalid server url", comment: "")) public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_error_response_", value: "Invalid response", comment: "")) @@ -73,7 +73,7 @@ public struct NKError: Error, Equatable, Sendable { public static let success = NKError(errorCode: 0, errorDescription: "") - /// ``serverMessage`` when the server messages is user friendly, otherwise `nil`. + /// The server's message when it's user friendly, otherwise `nil`. /// /// That covers OCS API errors with a 4xx status, which Nextcloud mostly translates (e.g. sharing and /// password policy), and WebDAV file name rule violations. Other server texts are technical. diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index f6a2b71a..37b4b709 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -12829,13 +12829,13 @@ } } }, - "You're offline. Check your internet connection." : { + "You're offline. Please check your internet connection." : { "comment" : "Error message when the device has no internet connection.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "You're offline. Check your internet connection." + "value" : "You're offline. Please check your internet connection." } } } diff --git a/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift b/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift index 95d1cc13..5905317a 100644 --- a/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift +++ b/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift @@ -5,9 +5,9 @@ import Foundation import NextcloudKit -extension NKError { +public extension NKError { /// - /// User friendly messages for generic errors. Each one fits a`kind`. + /// User friendly messages for generic errors. Each one fits a `kind`. /// var localizedGenericMessage: String { if let userFacingServerMessage { @@ -16,7 +16,7 @@ extension NKError { switch kind ?? .unknown { case .offline: - return String(localized: "You're offline. Check your internet connection.", bundle: .module, comment: "Error message when the device has no internet connection.") + return String(localized: "You're offline. Please check your internet connection.", bundle: .module, comment: "Error message when the device has no internet connection.") case .timeout: return String(localized: "The server took too long to respond. Try again later.", bundle: .module, comment: "Error message when a request to the server timed out.") From 0f16738329d93d54d25f8ed4368fc04f4df81be9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 15 Oct 2026 13:56:15 +0200 Subject: [PATCH 5/9] refactor(error): Move localizedGenericMessage from NextcloudKitUI to NextcloudKit Signed-off-by: Milen Pivchev --- .tx/config | 8 +- AGENTS.md | 2 +- REUSE.toml | 2 +- Sources/NextcloudKit/Localizable.xcstrings | 215 ++++++++++++++++++ .../NKError+localizedGenericMessage.swift | 41 ++-- Sources/NextcloudKitUI/Localizable.xcstrings | 209 ----------------- ...NKErrorLocalizedGenericMessageTests.swift} | 5 +- 7 files changed, 245 insertions(+), 237 deletions(-) create mode 100644 Sources/NextcloudKit/Localizable.xcstrings rename Sources/{NextcloudKitUI => NextcloudKit}/NKError+localizedGenericMessage.swift (71%) rename Tests/{NextcloudKitUITests/NKErrorLocalizedMessageTests.swift => NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift} (95%) diff --git a/.tx/config b/.tx/config index a72754ad..a17057a0 100644 --- a/.tx/config +++ b/.tx/config @@ -1,8 +1,14 @@ [main] host = https://www.transifex.com +[o:nextcloud:p:nextcloud:r:nextcloudkit] +file_filter = translations/nextcloudkit/.xcstrings +source_file = Sources/NextcloudKit/Localizable.xcstrings +source_lang = en +type = XCSTRINGS + [o:nextcloud:p:nextcloud:r:nextcloudkitui] -file_filter = translations/.xcstrings +file_filter = translations/nextcloudkitui/.xcstrings source_file = Sources/NextcloudKitUI/Localizable.xcstrings source_lang = en type = XCSTRINGS diff --git a/AGENTS.md b/AGENTS.md index daa9879b..0190b6e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Networking is built on Alamofire; responses are parsed with SwiftyJSON and Swift | Directory | Description | Agent Action | |-----------------|-----------------------------------------------------|----------------------| | `.github` | GitHub CI workflows (build & test, lint, REUSE compliance, documentation). | Keep all four checks green: build & test (`xcode.yml`), SwiftLint (`lint.yml`), REUSE compliance (`reuse.yml`), DocC build (`documentation.yml`). | -| `Sources/NextcloudKit` | Core library. Server APIs live in one `NextcloudKit+.swift` extension file per API area (e.g. `NextcloudKit+Share.swift`), next to core types like `NextcloudKit`, `NKCommon`, `NKSession` and `NKError`. Shared helpers live in `Extensions/`, `Log/`, `Utils/` and `TypeIdentifiers/`. | Add new server APIs as a dedicated `NextcloudKit+.swift` extension file. | +| `Sources/NextcloudKit` | Core library. Server APIs live in one `NextcloudKit+.swift` extension file per API area (e.g. `NextcloudKit+Share.swift`), next to core types like `NextcloudKit`, `NKCommon`, `NKSession` and `NKError`. Shared helpers live in `Extensions/`, `Log/`, `Utils/` and `TypeIdentifiers/`. `Localizable.xcstrings` holds the user-facing error messages (`NKError.localizedGenericMessage`). | Add new server APIs as a dedicated `NextcloudKit+.swift` extension file. Only add new localized strings to `Localizable.xcstrings` in English, with `NSLocalizedString(…, bundle: .module, comment:)` since `String(localized:)` needs newer deployment targets. | | `Sources/NextcloudKit/Models` | `NK*` data models returned by the APIs. | Place each new model in its own file; group related models for one feature in a subfolder (see `Models/Governance`). | | `Sources/NextcloudKitUI` | SwiftUI login and account UI used by client apps. Depends on CodeScanner for QR-code login. | Only add new localized strings to `Localizable.xcstrings` in English. | | `Tests/NextcloudKitUnitTests` | Unit tests; network requests are mocked with Mocker (see `LoginUnitTests.swift`). | Prefer this target for new tests. Try to add unit tests for new features, where applicable and makes sense; do not overcomplicate them. | diff --git a/REUSE.toml b/REUSE.toml index 4c92d24e..167d6a73 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -24,7 +24,7 @@ SPDX-FileCopyrightText = "2023 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "GPL-3.0-or-later" [[annotations]] -path = [".tx/config", "Sources/NextcloudKitUI/Documentation.docc/Documentation.md", "Sources/NextcloudKitUI/Documentation.docc/theme-settings.json", "Sources/NextcloudKitUI/Localizable.xcstrings"] +path = [".tx/config", "Sources/NextcloudKitUI/Documentation.docc/Documentation.md", "Sources/NextcloudKitUI/Documentation.docc/theme-settings.json", "Sources/NextcloudKitUI/Localizable.xcstrings", "Sources/NextcloudKit/Localizable.xcstrings"] precedence = "aggregate" SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "GPL-3.0-or-later" diff --git a/Sources/NextcloudKit/Localizable.xcstrings b/Sources/NextcloudKit/Localizable.xcstrings new file mode 100644 index 00000000..a9154349 --- /dev/null +++ b/Sources/NextcloudKit/Localizable.xcstrings @@ -0,0 +1,215 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "A secure connection to the server couldn't be established." : { + "comment" : "Error message for TLS and certificate failures.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "A secure connection to the server couldn't be established." + } + } + } + }, + "An item with this name already exists." : { + "comment" : "Error message when a file or folder with the same name already exists.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "An item with this name already exists." + } + } + } + }, + "Can't reach the server. Check the address or try again later." : { + "comment" : "Error message when the server cannot be reached.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Can't reach the server. Check the address or try again later." + } + } + } + }, + "Something went wrong. Please try again." : { + "comment" : "Error message when the cause of the error is unknown.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Something went wrong. Please try again." + } + } + } + }, + "The file is locked. Try again later." : { + "comment" : "Error message when the file is locked on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The file is locked. Try again later." + } + } + } + }, + "The file is too large for the server." : { + "comment" : "Error message when the server rejects a file because of its size.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The file is too large for the server." + } + } + } + }, + "The item could not be found on the server." : { + "comment" : "Error message when the requested file, folder or share does not exist on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The item could not be found on the server." + } + } + } + }, + "The item was changed on the server. Please refresh and try again." : { + "comment" : "Error message when the request conflicts with the current state on the server.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The item was changed on the server. Please refresh and try again." + } + } + } + }, + "The request was cancelled." : { + "comment" : "Error message when a request was cancelled.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The request was cancelled." + } + } + } + }, + "The server is temporarily unavailable. Try again later." : { + "comment" : "Error message when the server is unavailable, e.g. in maintenance mode.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server is temporarily unavailable. Try again later." + } + } + } + }, + "The server ran into a problem. Try again later." : { + "comment" : "Error message for internal server errors.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server ran into a problem. Try again later." + } + } + } + }, + "The server sent an unexpected response. Try again later." : { + "comment" : "Error message when the server's response cannot be read.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server sent an unexpected response. Try again later." + } + } + } + }, + "The server took too long to respond. Try again later." : { + "comment" : "Error message when a request to the server timed out.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server took too long to respond. Try again later." + } + } + } + }, + "There is not enough storage space left on the server." : { + "comment" : "Error message when the account's storage quota is used up.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is not enough storage space left on the server." + } + } + } + }, + "There is not enough storage space left on this device." : { + "comment" : "Error message when the device runs out of storage space.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is not enough storage space left on this device." + } + } + } + }, + "Too many requests. Please wait a moment and try again." : { + "comment" : "Error message when the server rate-limits the requests.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Too many requests. Please wait a moment and try again." + } + } + } + }, + "You don't have permission to do this." : { + "comment" : "Error message when the server forbids the action.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You don't have permission to do this." + } + } + } + }, + "You're offline. Please check your internet connection." : { + "comment" : "Error message when the device has no internet connection.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You're offline. Please check your internet connection." + } + } + } + }, + "Your login is no longer valid. Please log in again." : { + "comment" : "Error message when the server rejects the account's credentials.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your login is no longer valid. Please log in again." + } + } + } + } + }, + "version" : "1.0" +} diff --git a/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift similarity index 71% rename from Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift rename to Sources/NextcloudKit/NKError+localizedGenericMessage.swift index 5905317a..f5a58f25 100644 --- a/Sources/NextcloudKitUI/NKError+localizedGenericMessage.swift +++ b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift @@ -3,12 +3,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import NextcloudKit public extension NKError { - /// /// User friendly messages for generic errors. Each one fits a `kind`. - /// var localizedGenericMessage: String { if let userFacingServerMessage { return userFacingServerMessage @@ -16,61 +13,61 @@ public extension NKError { switch kind ?? .unknown { case .offline: - return String(localized: "You're offline. Please check your internet connection.", bundle: .module, comment: "Error message when the device has no internet connection.") + return NSLocalizedString("You're offline. Please check your internet connection.", bundle: .module, comment: "Error message when the device has no internet connection.") case .timeout: - return String(localized: "The server took too long to respond. Try again later.", bundle: .module, comment: "Error message when a request to the server timed out.") + return NSLocalizedString("The server took too long to respond. Try again later.", bundle: .module, comment: "Error message when a request to the server timed out.") case .serverUnreachable: - return String(localized: "Can't reach the server. Check the address or try again later.", bundle: .module, comment: "Error message when the server cannot be reached.") + return NSLocalizedString("Can't reach the server. Check the address or try again later.", bundle: .module, comment: "Error message when the server cannot be reached.") case .secureConnection: - return String(localized: "A secure connection to the server couldn't be established.", bundle: .module, comment: "Error message for TLS and certificate failures.") + return NSLocalizedString("A secure connection to the server couldn't be established.", bundle: .module, comment: "Error message for TLS and certificate failures.") case .unauthorized: - return String(localized: "Your login is no longer valid. Please log in again.", bundle: .module, comment: "Error message when the server rejects the account's credentials.") + return NSLocalizedString("Your login is no longer valid. Please log in again.", bundle: .module, comment: "Error message when the server rejects the account's credentials.") case .forbidden: - return String(localized: "You don't have permission to do this.", bundle: .module, comment: "Error message when the server forbids the action.") + return NSLocalizedString("You don't have permission to do this.", bundle: .module, comment: "Error message when the server forbids the action.") case .notFound: - return String(localized: "The item could not be found on the server.", bundle: .module, comment: "Error message when the requested file, folder or share does not exist on the server.") + return NSLocalizedString("The item could not be found on the server.", bundle: .module, comment: "Error message when the requested file, folder or share does not exist on the server.") case .alreadyExists: - return String(localized: "An item with this name already exists.", bundle: .module, comment: "Error message when a file or folder with the same name already exists.") + return NSLocalizedString("An item with this name already exists.", bundle: .module, comment: "Error message when a file or folder with the same name already exists.") case .conflict: - return String(localized: "The item was changed on the server. Please refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") + return NSLocalizedString("The item was changed on the server. Please refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") case .locked: - return String(localized: "The file is locked. Try again later.", bundle: .module, comment: "Error message when the file is locked on the server.") + return NSLocalizedString("The file is locked. Try again later.", bundle: .module, comment: "Error message when the file is locked on the server.") case .quotaExceeded: - return String(localized: "There is not enough storage space left on the server.", bundle: .module, comment: "Error message when the account's storage quota is used up.") + return NSLocalizedString("There is not enough storage space left on the server.", bundle: .module, comment: "Error message when the account's storage quota is used up.") case .tooLarge: - return String(localized: "The file is too large for the server.", bundle: .module, comment: "Error message when the server rejects a file because of its size.") + return NSLocalizedString("The file is too large for the server.", bundle: .module, comment: "Error message when the server rejects a file because of its size.") case .rateLimited: - return String(localized: "Too many requests. Please wait a moment and try again.", bundle: .module, comment: "Error message when the server rate-limits the requests.") + return NSLocalizedString("Too many requests. Please wait a moment and try again.", bundle: .module, comment: "Error message when the server rate-limits the requests.") case .unavailable: - return String(localized: "The server is temporarily unavailable. Try again later.", bundle: .module, comment: "Error message when the server is unavailable, e.g. in maintenance mode.") + return NSLocalizedString("The server is temporarily unavailable. Try again later.", bundle: .module, comment: "Error message when the server is unavailable, e.g. in maintenance mode.") case .serverError: - return String(localized: "The server ran into a problem. Try again later.", bundle: .module, comment: "Error message for internal server errors.") + return NSLocalizedString("The server ran into a problem. Try again later.", bundle: .module, comment: "Error message for internal server errors.") case .invalidResponse: - return String(localized: "The server sent an unexpected response. Try again later.", bundle: .module, comment: "Error message when the server's response cannot be read.") + return NSLocalizedString("The server sent an unexpected response. Try again later.", bundle: .module, comment: "Error message when the server's response cannot be read.") case .localStorageFull: - return String(localized: "There is not enough storage space left on this device.", bundle: .module, comment: "Error message when the device runs out of storage space.") + return NSLocalizedString("There is not enough storage space left on this device.", bundle: .module, comment: "Error message when the device runs out of storage space.") case .cancelled: - return String(localized: "The request was cancelled.", bundle: .module, comment: "Error message when a request was cancelled.") + return NSLocalizedString("The request was cancelled.", bundle: .module, comment: "Error message when a request was cancelled.") case .unknown: - return String(localized: "Something went wrong. Please try again.", bundle: .module, comment: "Error message when the cause of the error is unknown.") + return NSLocalizedString("Something went wrong. Please try again.", bundle: .module, comment: "Error message when the cause of the error is unknown.") } } } diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index 37b4b709..14c565e2 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -601,17 +601,6 @@ } } }, - "A secure connection to the server couldn't be established." : { - "comment" : "Error message for TLS and certificate failures.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A secure connection to the server couldn't be established." - } - } - } - }, "Accounts" : { "comment" : "Navigation title for the list of configured Nextcloud accounts.", "localizations" : { @@ -1172,17 +1161,6 @@ } } }, - "An item with this name already exists." : { - "comment" : "Error message when a file or folder with the same name already exists.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "An item with this name already exists." - } - } - } - }, "Apply permissions to all recipients?" : { "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", "localizations" : { @@ -1588,17 +1566,6 @@ } } }, - "Can't reach the server. Check the address or try again later." : { - "comment" : "Error message when the server cannot be reached.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Can't reach the server. Check the address or try again later." - } - } - } - }, "Cancel" : { "comment" : "Button that dismisses a confirmation dialog without making changes.", "localizations" : { @@ -12010,138 +11977,6 @@ } } }, - "Something went wrong. Please try again." : { - "comment" : "Error message when the cause of the error is unknown.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Something went wrong. Please try again." - } - } - } - }, - "The file is locked. Try again later." : { - "comment" : "Error message when the file is locked on the server.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file is locked. Try again later." - } - } - } - }, - "The file is too large for the server." : { - "comment" : "Error message when the server rejects a file because of its size.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file is too large for the server." - } - } - } - }, - "The item could not be found on the server." : { - "comment" : "Error message when the requested file, folder or share does not exist on the server.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The item could not be found on the server." - } - } - } - }, - "The item was changed on the server. Please refresh and try again." : { - "comment" : "Error message when the request conflicts with the current state on the server.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The item was changed on the server. Please refresh and try again." - } - } - } - }, - "The request was cancelled." : { - "comment" : "Error message when a request was cancelled.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The request was cancelled." - } - } - } - }, - "The server is temporarily unavailable. Try again later." : { - "comment" : "Error message when the server is unavailable, e.g. in maintenance mode.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server is temporarily unavailable. Try again later." - } - } - } - }, - "The server ran into a problem. Try again later." : { - "comment" : "Error message for internal server errors.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server ran into a problem. Try again later." - } - } - } - }, - "The server sent an unexpected response. Try again later." : { - "comment" : "Error message when the server's response cannot be read.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server sent an unexpected response. Try again later." - } - } - } - }, - "The server took too long to respond. Try again later." : { - "comment" : "Error message when a request to the server timed out.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server took too long to respond. Try again later." - } - } - } - }, - "There is not enough storage space left on the server." : { - "comment" : "Error message when the account's storage quota is used up.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There is not enough storage space left on the server." - } - } - } - }, - "There is not enough storage space left on this device." : { - "comment" : "Error message when the device runs out of storage space.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There is not enough storage space left on this device." - } - } - } - }, "This change will replace the individual permission settings of all recipients in this share." : { "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", "localizations" : { @@ -12806,50 +12641,6 @@ } } } - }, - "Too many requests. Please wait a moment and try again." : { - "comment" : "Error message when the server rate-limits the requests.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Too many requests. Please wait a moment and try again." - } - } - } - }, - "You don't have permission to do this." : { - "comment" : "Error message when the server forbids the action.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You don't have permission to do this." - } - } - } - }, - "You're offline. Please check your internet connection." : { - "comment" : "Error message when the device has no internet connection.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You're offline. Please check your internet connection." - } - } - } - }, - "Your login is no longer valid. Please log in again." : { - "comment" : "Error message when the server rejects the account's credentials.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your login is no longer valid. Please log in again." - } - } - } } }, "version" : "1.0" diff --git a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift similarity index 95% rename from Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift rename to Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift index 2f0a47a7..f1b065d3 100644 --- a/Tests/NextcloudKitUITests/NKErrorLocalizedMessageTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift @@ -3,14 +3,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import NextcloudKit -@testable import NextcloudKitUI import Testing +@testable import NextcloudKit /// /// Tests for the message ``NKError/localizedGenericMessage`` shows to users. /// -struct NKErrorLocalizedMessageTests { +struct NKErrorLocalizedGenericMessageTests { @Test(arguments: NKError.Kind.allCases) func kindMessage(kind: NKError.Kind) { let message = NKError(errorCode: -1, errorDescription: "Technical description", kind: kind).localizedGenericMessage From 33c9af5e4fe5b209010b7544e2e929760e2125c3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 19 Oct 2026 18:57:37 +0200 Subject: [PATCH 6/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/Localizable.xcstrings | 585 ++- .../NextcloudKit/Log/NKLogFileManager.swift | 8 +- Sources/NextcloudKit/NKError+Kind.swift | 11 +- .../NKError+localizedGenericMessage.swift | 7 +- Sources/NextcloudKit/NKError.swift | 205 +- .../Utils/FileNameValidator.swift | 10 +- Sources/NextcloudKitUI/Localizable.xcstrings | 3315 +++++++++-------- .../Views/Login/LoginFlowModel.swift | 2 +- .../NKErrorKindTests.swift | 125 +- .../NKErrorLocalizedGenericMessageTests.swift | 19 +- 10 files changed, 2398 insertions(+), 1889 deletions(-) diff --git a/Sources/NextcloudKit/Localizable.xcstrings b/Sources/NextcloudKit/Localizable.xcstrings index a9154349..971076cd 100644 --- a/Sources/NextcloudKit/Localizable.xcstrings +++ b/Sources/NextcloudKit/Localizable.xcstrings @@ -1,6 +1,28 @@ { "sourceLanguage" : "en", "strings" : { + "\"%@\" is a forbidden name." : { + "comment" : "Error message when the server reserves a name. %@ is the name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "\"%@\" is a forbidden name." + } + } + } + }, + ".\"%@\" is a forbidden file extension." : { + "comment" : "Error message when the server forbids a file extension. %@ is the extension.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : ".\"%@\" is a forbidden file extension." + } + } + } + }, "A secure connection to the server couldn't be established." : { "comment" : "Error message for TLS and certificate failures.", "localizations" : { @@ -23,6 +45,28 @@ } } }, + "Bad gateway" : { + "comment" : "Error message when a proxy got an invalid response from the server (HTTP 502).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bad gateway" + } + } + } + }, + "Bad request" : { + "comment" : "Error message when the server cannot process the request (HTTP 400).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bad request" + } + } + } + }, "Can't reach the server. Check the address or try again later." : { "comment" : "Error message when the server cannot be reached.", "localizations" : { @@ -34,6 +78,325 @@ } } }, + "Cancelled" : { + "comment" : "Error message when a request was cancelled.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancelled" + } + } + } + }, + "Compact" : { + "comment" : "Log level that logs only the essentials.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compact" + } + } + } + }, + "Connection SSL error, try again" : { + "comment" : "Error message when a secure connection to the server failed.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connection SSL error, try again" + } + } + } + }, + "Create folder error" : { + "comment" : "Error message when the folder for a chunked upload cannot be created.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create folder error" + } + } + } + }, + "Disabled" : { + "comment" : "Log level that turns logging off.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disabled" + } + } + } + }, + "Expectation failed" : { + "comment" : "Error message when the server cannot meet the request's expectation (HTTP 417).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expectation failed" + } + } + } + }, + "File name cannot be empty." : { + "comment" : "Error message when a file or folder name is empty.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "File name cannot be empty." + } + } + } + }, + "File not found" : { + "comment" : "Error message when the file to upload in chunks cannot be found.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "File not found" + } + } + } + }, + "Files not found" : { + "comment" : "Error message when a chunked upload has no chunks to upload.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Files not found" + } + } + } + }, + "Forbidden" : { + "comment" : "Error message when the server forbids the request.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forbidden" + } + } + } + }, + "Gateway timeout" : { + "comment" : "Error message when a proxy timed out waiting for the server (HTTP 504).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway timeout" + } + } + } + }, + "Generic error" : { + "comment" : "Error message when no more specific description is available.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Generic error" + } + } + } + }, + "Internal error" : { + "comment" : "Error message for an internal error in the app.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Internal error" + } + } + } + }, + "Internal server error" : { + "comment" : "Error message when the server ran into an error (HTTP 500).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Internal server error" + } + } + } + }, + "Invalid data format" : { + "comment" : "Error message when data in the server's response cannot be read.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid data format" + } + } + } + }, + "Invalid date format" : { + "comment" : "Error message when a date in the server's response cannot be read.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid date format" + } + } + } + }, + "Invalid response" : { + "comment" : "Error message when the server's response is not valid.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid response" + } + } + } + }, + "Invalid response, error decoding XML" : { + "comment" : "Error message when the server's XML response cannot be read.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid response, error decoding XML" + } + } + } + }, + "Invalid server URL" : { + "comment" : "Error message when the server address is not a valid URL.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid server URL" + } + } + } + }, + "It is not possible to connect to the server at this time" : { + "comment" : "Error message when the connection to the server failed.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "It is not possible to connect to the server at this time" + } + } + } + }, + "Move file error" : { + "comment" : "Error message when the uploaded chunks cannot be assembled into the file.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Move file error" + } + } + } + }, + "Name contains an invalid character: \"%@\"." : { + "comment" : "Error message when a name contains a forbidden character. %@ is the character.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name contains an invalid character: \"%@\"." + } + } + } + }, + "Name must not contain spaces at the beginning or end." : { + "comment" : "Error message when a name starts or ends with a space.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name must not contain spaces at the beginning or end." + } + } + } + }, + "Normal" : { + "comment" : "Default log level.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Normal" + } + } + } + }, + "Not enough memory" : { + "comment" : "Error message when the device lacks the space to split a file into chunks.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Not enough memory" + } + } + } + }, + "Request timeout" : { + "comment" : "Error message when the server timed out waiting for the request (HTTP 408).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Request timeout" + } + } + } + }, + "Resource not modified" : { + "comment" : "Error message when the resource was not modified (HTTP 304).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resource not modified" + } + } + } + }, + "Server connection error" : { + "comment" : "Error message when the device has no internet connection.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server connection error" + } + } + } + }, + "Server is currently in maintenance mode" : { + "comment" : "Error message when the server is in maintenance mode (HTTP 503).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server is currently in maintenance mode" + } + } + } + }, "Something went wrong. Please try again." : { "comment" : "Error message when the cause of the error is unknown.", "localizations" : { @@ -45,6 +408,28 @@ } } }, + "Storage quota is reached" : { + "comment" : "Error message when the account's storage quota is used up (HTTP 507).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Storage quota is reached" + } + } + } + }, + "The certificate for this server is invalid" : { + "comment" : "Error message when the server's certificate is not trusted.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The certificate for this server is invalid" + } + } + } + }, "The file is locked. Try again later." : { "comment" : "Error message when the file is locked on the server.", "localizations" : { @@ -56,6 +441,17 @@ } } }, + "The file is too large" : { + "comment" : "Error message when the file is too large for the server (HTTP 413).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The file is too large" + } + } + } + }, "The file is too large for the server." : { "comment" : "Error message when the server rejects a file because of its size.", "localizations" : { @@ -89,6 +485,28 @@ } } }, + "The name contains at least one invalid character" : { + "comment" : "Error message when the server rejects a name because of invalid characters.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The name contains at least one invalid character" + } + } + } + }, + "The request could not be completed due to a conflict with the current state of the resource" : { + "comment" : "Error message when the request conflicts with the current state on the server (HTTP 409).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The request could not be completed due to a conflict with the current state of the resource" + } + } + } + }, "The request was cancelled." : { "comment" : "Error message when a request was cancelled.", "localizations" : { @@ -100,6 +518,50 @@ } } }, + "The requested method is not supported" : { + "comment" : "Error message when the server doesn't allow the request method (HTTP 405).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The requested method is not supported" + } + } + } + }, + "The requested resource could not be found" : { + "comment" : "Error message when the file, folder or resource does not exist (HTTP 404).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The requested resource could not be found" + } + } + } + }, + "The server appears to be down" : { + "comment" : "Error message when the server cannot be reached.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server appears to be down" + } + } + } + }, + "The server does not meet one of the preconditions of the request" : { + "comment" : "Error message when a precondition of the request failed (HTTP 412).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server does not meet one of the preconditions of the request" + } + } + } + }, "The server is temporarily unavailable. Try again later." : { "comment" : "Error message when the server is unavailable, e.g. in maintenance mode.", "localizations" : { @@ -166,6 +628,39 @@ } } }, + "This name isn't allowed." : { + "comment" : "Error message when the server rejects a file or folder name.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This name isn't allowed." + } + } + } + }, + "Time out" : { + "comment" : "Error message when a request timed out.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Time out" + } + } + } + }, + "Too many requests" : { + "comment" : "Error message when the server rate-limits the requests (HTTP 429).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Too many requests" + } + } + } + }, "Too many requests. Please wait a moment and try again." : { "comment" : "Error message when the server rate-limits the requests.", "localizations" : { @@ -177,6 +672,94 @@ } } }, + "Transfer stopped" : { + "comment" : "Error message when a transfer was stopped.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Transfer stopped" + } + } + } + }, + "Unauthorized" : { + "comment" : "Error message when the server rejects the login (HTTP 401).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unauthorized" + } + } + } + }, + "Unavailable" : { + "comment" : "Error message when the server is unavailable.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unavailable" + } + } + } + }, + "User authentication required" : { + "comment" : "Error message when the server requires a login.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "User authentication required" + } + } + } + }, + "Verbose" : { + "comment" : "Log level that logs everything, for debugging.", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbose" + } + } + } + }, + "WebDAV Locked: Trying to access locked resource" : { + "comment" : "Error message when the file is locked (HTTP 423).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "WebDAV Locked: Trying to access locked resource" + } + } + } + }, + "WebDAV multistatus" : { + "comment" : "Error message for a WebDAV Multi-Status response (HTTP 207).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "WebDAV multistatus" + } + } + } + }, + "You don't have permission to complete the operation" : { + "comment" : "Error message when the server forbids the operation (HTTP 403).", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You don't have permission to complete the operation" + } + } + } + }, "You don't have permission to do this." : { "comment" : "Error message when the server forbids the action.", "localizations" : { @@ -212,4 +795,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Sources/NextcloudKit/Log/NKLogFileManager.swift b/Sources/NextcloudKit/Log/NKLogFileManager.swift index a63ee3f9..0fa9b1fa 100644 --- a/Sources/NextcloudKit/Log/NKLogFileManager.swift +++ b/Sources/NextcloudKit/Log/NKLogFileManager.swift @@ -25,10 +25,10 @@ public enum NKLogLevel: Int, CaseIterable, Identifiable, Comparable { // For Picker display public var displayText: String { switch self { - case .disabled: return NSLocalizedString("_disabled_", comment: "") - case .compact: return NSLocalizedString("_compact_", comment: "") - case .normal: return NSLocalizedString("_normal_", comment: "") - case .verbose: return NSLocalizedString("_verbose_", comment: "") + case .disabled: return NSLocalizedString("Disabled", bundle: .module, comment: "Log level that turns logging off.") + case .compact: return NSLocalizedString("Compact", bundle: .module, comment: "Log level that logs only the essentials.") + case .normal: return NSLocalizedString("Normal", bundle: .module, comment: "Default log level.") + case .verbose: return NSLocalizedString("Verbose", bundle: .module, comment: "Log level that logs everything, for debugging.") } } diff --git a/Sources/NextcloudKit/NKError+Kind.swift b/Sources/NextcloudKit/NKError+Kind.swift index 1d41ffbb..4a9d3401 100644 --- a/Sources/NextcloudKit/NKError+Kind.swift +++ b/Sources/NextcloudKit/NKError+Kind.swift @@ -8,7 +8,7 @@ import Foundation public extension NKError { /// What went wrong, independent of how the server or the system worded it. /// - /// Choose the message shown to people from this, and keep ``NKError/errorDescription`` for logs. + /// Choose the message shown to users from this, and keep ``NKError/errorDescription`` for logs. enum Kind: Sendable, CaseIterable { case offline case timeout @@ -19,6 +19,8 @@ public extension NKError { case notFound /// `MKCOL` on an existing folder, or `COPY`/`MOVE` onto an existing item without overwriting it. case alreadyExists + /// The server doesn't accept the file or folder name. + case invalidName case conflict case locked case quotaExceeded @@ -35,6 +37,13 @@ public extension NKError { } extension NKError.Kind { + /// Classifies the WebDAV exceptions that the HTTP status alone doesn't tell apart. + init?(davException: String?) { + guard davException == "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath" else { return nil } + + self = .invalidName + } + /// Classifies an ``NKError/errorCode``: a URL loading error, an HTTP status or an OCS status code. /// /// Returns `nil` for `0`, which means success. The request method tells some WebDAV statuses apart. diff --git a/Sources/NextcloudKit/NKError+localizedGenericMessage.swift b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift index f5a58f25..e7dabf1e 100644 --- a/Sources/NextcloudKit/NKError+localizedGenericMessage.swift +++ b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift @@ -7,8 +7,8 @@ import Foundation public extension NKError { /// User friendly messages for generic errors. Each one fits a `kind`. var localizedGenericMessage: String { - if let userFacingServerMessage { - return userFacingServerMessage + if let userFriendlyServerMessage { + return userFriendlyServerMessage } switch kind ?? .unknown { @@ -36,6 +36,9 @@ public extension NKError { case .alreadyExists: return NSLocalizedString("An item with this name already exists.", bundle: .module, comment: "Error message when a file or folder with the same name already exists.") + case .invalidName: + return NSLocalizedString("This name isn't allowed.", bundle: .module, comment: "Error message when the server rejects a file or folder name.") + case .conflict: return NSLocalizedString("The item was changed on the server. Please refresh and try again.", bundle: .module, comment: "Error message when the request conflicts with the current state on the server.") diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index 616ea79f..84454027 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -30,10 +30,8 @@ extension OCSPath { static var ocsMetaCode: Self { ["ocs", "meta", "statuscode"] } static var ocsMetaMsg: Self { ["ocs", "meta", "message"] } static var ocsDataMsg: Self { ["ocs", "data", "message"] } - static var ocsData: Self { ["ocs", "data"] } static var ocsXMLMsg: Self { ["d:error", "s:message"] } static var davException: Self { ["d:error", "s:exception"] } - static var jsonMsg: Self { ["message"] } } public struct NKError: Error, Equatable, Sendable { @@ -44,51 +42,36 @@ public struct NKError: Error, Equatable, Sendable { public let error: Error public let responseData: Data? - /// What went wrong, for choosing the message shown to people; `nil` for ``success``. + /// What went wrong, for choosing the message shown to users; `nil` for ``success``. public let kind: Kind? - /// The HTTP status code of the response, when there was one. - let httpStatusCode: Int? - /// The `ocs.meta.statuscode` of an OCS response. - let ocsStatusCode: Int? - /// The text the server sent with the error: an OCS message, the WebDAV `s:message` or a JSON `message`. + /// The OCS message the server sent with the error. let serverMessage: String? - /// The WebDAV exception class from `s:exception`, e.g. `Sabre\DAV\Exception\Conflict`. - let davException: String? - - public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: NSLocalizedString("_invalid_url_", value: "Invalid server url", comment: "")) - public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_error_response_", value: "Invalid response", comment: "")) - public static let xmlError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_error_decode_xml_", value: "Invalid response, error decoding XML", comment: "")) - public static let invalidDate = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("_invalid_date_format_", value: "Invalid date format", comment: "")) - public static let invalidData = NKError(errorCode: NSURLErrorCannotDecodeContentData, errorDescription: NSLocalizedString("_invalid_data_format_", value: "Invalid data format", comment: "")) - public static let unauthorizedError = NKError(errorCode: 401, errorDescription: NSLocalizedString("_unauthorized_", value: "Unauthorized", comment: "")) - public static let unavailableError = NKError(errorCode: 503, errorDescription: NSLocalizedString("_Unavailable_", value: "Unavailable", comment: "")) - public static let forbiddenError = NKError(errorCode: 403, errorDescription: NSLocalizedString("_forbidden_", value: "Forbidden", comment: "")) - public static let cancelled = NKError(errorCode: -999, errorDescription: NSLocalizedString("_cancelled_", value: "Cancelled", comment: "")) - - public static let errorChunkFileNull = NKError(errorCode: -9994, errorDescription: NSLocalizedString("_error_file_null_", value: "File not found", comment: "")) - public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: NSLocalizedString("_chunk_files_empty_", value: "Files not found", comment: "")) - public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: NSLocalizedString("_error_create_folder_", value: "Create folder error", comment: "")) - public static let errorChunkMoveFile = NKError(errorCode: -9997, errorDescription: NSLocalizedString("_error_move_folder_", value: "Move file error", comment: "")) - public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: NSLocalizedString("_no_enough_memory_", value: "No enough memory", comment: ""), kind: .localStorageFull) + /// The URL path of the request that failed, when known. + private(set) var requestPath: String? + + public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: NSLocalizedString("Invalid server URL", bundle: .module, comment: "Error message when the server address is not a valid URL.")) + public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid response", bundle: .module, comment: "Error message when the server's response is not valid.")) + public static let xmlError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid response, error decoding XML", bundle: .module, comment: "Error message when the server's XML response cannot be read.")) + public static let invalidDate = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid date format", bundle: .module, comment: "Error message when a date in the server's response cannot be read.")) + public static let invalidData = NKError(errorCode: NSURLErrorCannotDecodeContentData, errorDescription: NSLocalizedString("Invalid data format", bundle: .module, comment: "Error message when data in the server's response cannot be read.")) + public static let unauthorizedError = NKError(errorCode: 401, errorDescription: NSLocalizedString("Unauthorized", bundle: .module, comment: "Error message when the server rejects the login (HTTP 401).")) + public static let unavailableError = NKError(errorCode: 503, errorDescription: NSLocalizedString("Unavailable", bundle: .module, comment: "Error message when the server is unavailable.")) + public static let forbiddenError = NKError(errorCode: 403, errorDescription: NSLocalizedString("Forbidden", bundle: .module, comment: "Error message when the server forbids the request.")) + public static let cancelled = NKError(errorCode: -999, errorDescription: NSLocalizedString("Cancelled", bundle: .module, comment: "Error message when a request was cancelled.")) + + public static let errorChunkFileNull = NKError(errorCode: -9994, errorDescription: NSLocalizedString("File not found", bundle: .module, comment: "Error message when the file to upload in chunks cannot be found.")) + public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: NSLocalizedString("Files not found", bundle: .module, comment: "Error message when a chunked upload has no chunks to upload.")) + public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: NSLocalizedString("Create folder error", bundle: .module, comment: "Error message when the folder for a chunked upload cannot be created.")) + public static let errorChunkMoveFile = NKError(errorCode: -9997, errorDescription: NSLocalizedString("Move file error", bundle: .module, comment: "Error message when the uploaded chunks cannot be assembled into the file.")) + public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: NSLocalizedString("Not enough memory", bundle: .module, comment: "Error message when the device lacks the space to split a file into chunks."), kind: .localStorageFull) public static let success = NKError(errorCode: 0, errorDescription: "") - /// The server's message when it's user friendly, otherwise `nil`. - /// - /// That covers OCS API errors with a 4xx status, which Nextcloud mostly translates (e.g. sharing and - /// password policy), and WebDAV file name rule violations. Other server texts are technical. - public var userFacingServerMessage: String? { - guard let serverMessage else { return nil } - - if let ocsStatusCode, 400..<500 ~= ocsStatusCode { - return serverMessage - } + /// The share API's error message, which Nextcloud writes for users. Other server texts are technical. + public var userFriendlyServerMessage: String? { + guard requestPath?.contains("/apps/files_sharing/api/v1/shares") == true else { return nil } - if davException == "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath" { - return serverMessage - } - - return nil + return serverMessage } /// Returns a localized user-facing description for a known error code. @@ -98,103 +81,103 @@ public struct NKError: Error, Equatable, Sendable { public static func getErrorDescription(for code: Int) -> String? { switch code { case -9999: - return NSLocalizedString("_internal_server_", value: "Internal error", comment: "") + return NSLocalizedString("Internal error", bundle: .module, comment: "Error message for an internal error in the app.") case -1001: - return NSLocalizedString("_time_out_", value: "Time out", comment: "") + return NSLocalizedString("Time out", bundle: .module, comment: "Error message when a request timed out.") case -1004: - return NSLocalizedString("_server_down_", value: "The server appears to be down", comment: "") + return NSLocalizedString("The server appears to be down", bundle: .module, comment: "Error message when the server cannot be reached.") case -1005: - return NSLocalizedString("_not_possible_connect_to_server_", value: "It is not possible to connect to the server at this time", comment: "") + return NSLocalizedString("It is not possible to connect to the server at this time", bundle: .module, comment: "Error message when the connection to the server failed.") case -1009: - return NSLocalizedString("_not_connected_internet_", value: "Server connection error", comment: "") + return NSLocalizedString("Server connection error", bundle: .module, comment: "Error message when the device has no internet connection.") case -1011: - return NSLocalizedString("_error_", value: "Generic error", comment: "") + return NSLocalizedString("Generic error", bundle: .module, comment: "Error message when no more specific description is available.") case -1012: - return NSLocalizedString("_not_possible_connect_to_server_", value: "It is not possible to connect to the server at this time", comment: "") + return NSLocalizedString("It is not possible to connect to the server at this time", bundle: .module, comment: "Error message when the connection to the server failed.") case -1013: - return NSLocalizedString("_user_authentication_required_", value: "User authentication required", comment: "") + return NSLocalizedString("User authentication required", bundle: .module, comment: "Error message when the server requires a login.") case -1200: - return NSLocalizedString("_ssl_connection_error_", value: "Connection SSL error, try again", comment: "") + return NSLocalizedString("Connection SSL error, try again", bundle: .module, comment: "Error message when a secure connection to the server failed.") case -1202: - return NSLocalizedString("_ssl_certificate_untrusted_", value: "The certificate for this server is invalid", comment: "") + return NSLocalizedString("The certificate for this server is invalid", bundle: .module, comment: "Error message when the server's certificate is not trusted.") case 0: return "" case 101: - return NSLocalizedString("_forbidden_characters_from_server_", value: "The name contains at least one invalid character", comment: "") + return NSLocalizedString("The name contains at least one invalid character", bundle: .module, comment: "Error message when the server rejects a name because of invalid characters.") case 200: - return NSLocalizedString("_transfer_stopped_", value: "Transfer stopped", comment: "") + return NSLocalizedString("Transfer stopped", bundle: .module, comment: "Error message when a transfer was stopped.") case 207: - return NSLocalizedString("_error_multi_status_", value: "WebDAV multistatus", comment: "") + return NSLocalizedString("WebDAV multistatus", bundle: .module, comment: "Error message for a WebDAV Multi-Status response (HTTP 207).") case 304: - return NSLocalizedString("_error_not_modified_", value: "Resource not modified", comment: "") + return NSLocalizedString("Resource not modified", bundle: .module, comment: "Error message when the resource was not modified (HTTP 304).") case 400: - return NSLocalizedString("_bad_request_", value: "Bad request", comment: "") + return NSLocalizedString("Bad request", bundle: .module, comment: "Error message when the server cannot process the request (HTTP 400).") case 401: - return NSLocalizedString("_unauthorized_", value: "Unauthorized", comment: "") + return NSLocalizedString("Unauthorized", bundle: .module, comment: "Error message when the server rejects the login (HTTP 401).") case 403: - return NSLocalizedString("_error_not_permission_", value: "You don't have permission to complete the operation", comment: "") + return NSLocalizedString("You don't have permission to complete the operation", bundle: .module, comment: "Error message when the server forbids the operation (HTTP 403).") case 404: - return NSLocalizedString("_error_not_found_", value: "The requested resource could not be found", comment: "") + return NSLocalizedString("The requested resource could not be found", bundle: .module, comment: "Error message when the file, folder or resource does not exist (HTTP 404).") case 405: - return NSLocalizedString("_method_not_allowed_", value: "The requested method is not supported", comment: "") + return NSLocalizedString("The requested method is not supported", bundle: .module, comment: "Error message when the server doesn't allow the request method (HTTP 405).") case 408: - return NSLocalizedString("_request_timeout_", value: "Request timeout", comment: "") + return NSLocalizedString("Request timeout", bundle: .module, comment: "Error message when the server timed out waiting for the request (HTTP 408).") case 409: - return NSLocalizedString("_error_conflict_", value: "The request could not be completed due to a conflict with the current state of the resource", comment: "") + return NSLocalizedString("The request could not be completed due to a conflict with the current state of the resource", bundle: .module, comment: "Error message when the request conflicts with the current state on the server (HTTP 409).") case 412: - return NSLocalizedString("_error_precondition_", value: "The server does not meet one of the preconditions that the requester", comment: "") + return NSLocalizedString("The server does not meet one of the preconditions of the request", bundle: .module, comment: "Error message when a precondition of the request failed (HTTP 412).") case 413: - return NSLocalizedString("_request_entity_too_large_", value: "The file is too large", comment: "") + return NSLocalizedString("The file is too large", bundle: .module, comment: "Error message when the file is too large for the server (HTTP 413).") case 417: - return NSLocalizedString("_expectation_failed_", value: "Expectation failed", comment: "") + return NSLocalizedString("Expectation failed", bundle: .module, comment: "Error message when the server cannot meet the request's expectation (HTTP 417).") case 423: - return NSLocalizedString("_webdav_locked_", value: "WebDAV Locked: Trying to access locked resource", comment: "") + return NSLocalizedString("WebDAV Locked: Trying to access locked resource", bundle: .module, comment: "Error message when the file is locked (HTTP 423).") case 429: - return NSLocalizedString("_too_many_requests_", value: "Too many requests", comment: "") + return NSLocalizedString("Too many requests", bundle: .module, comment: "Error message when the server rate-limits the requests (HTTP 429).") case 500: - return NSLocalizedString("_internal_server_", value: "Internal server error", comment: "") + return NSLocalizedString("Internal server error", bundle: .module, comment: "Error message when the server ran into an error (HTTP 500).") case 502: - return NSLocalizedString("_bad_gateway_", value: "Bad gateway", comment: "") + return NSLocalizedString("Bad gateway", bundle: .module, comment: "Error message when a proxy got an invalid response from the server (HTTP 502).") case 503: - return NSLocalizedString("_server_maintenance_mode_", value: "Server is currently in maintenance mode", comment: "") + return NSLocalizedString("Server is currently in maintenance mode", bundle: .module, comment: "Error message when the server is in maintenance mode (HTTP 503).") case 504: - return NSLocalizedString("_gateway_timeout_", value: "Gateway timeout", comment: "") + return NSLocalizedString("Gateway timeout", bundle: .module, comment: "Error message when a proxy timed out waiting for the server (HTTP 504).") case 507: - return NSLocalizedString("_user_over_quota_", value: "Storage quota is reached", comment: "") + return NSLocalizedString("Storage quota is reached", bundle: .module, comment: "Error message when the account's storage quota is used up (HTTP 507).") case NSURLErrorCannotDecodeContentData: - return NSLocalizedString("_invalid_data_format_", value: "Invalid data format", comment: "") + return NSLocalizedString("Invalid data format", bundle: .module, comment: "Error message when data in the server's response cannot be read.") default: return nil @@ -212,33 +195,12 @@ public struct NKError: Error, Equatable, Sendable { let description = HTTPURLResponse.localizedString(forStatusCode: statusCode) if description.isEmpty { - return NSLocalizedString("_error_", value: "Generic error", comment: "") + return NSLocalizedString("Generic error", bundle: .module, comment: "Error message when no more specific description is available.") } return description } - /// The OCS status code, server message and WebDAV exception found in an error response body. - private static func details(of data: Data?) -> (ocsStatusCode: Int?, serverMessage: String?, davException: String?) { - guard let data, !data.isEmpty else { return (nil, nil, nil) } - - if let json = try? JSON(data: data) { - return (json[.ocsMetaCode].int, serverMessage(in: json), nil) - } - - let xml = XML.parse(data) - - return (xml[.ocsMetaCode].int, serverMessage(in: xml), firstNonEmpty(xml[.davException].text)) - } - - private static func serverMessage(in json: JSON) -> String? { - firstNonEmpty(json[.ocsDataMsg].string, json[.ocsData].string, json[.ocsMetaMsg].string, json[.jsonMsg].string) - } - - private static func serverMessage(in xml: XML.Accessor) -> String? { - firstNonEmpty(xml[.ocsDataMsg].text, xml[.ocsMetaMsg].text, xml[.ocsXMLMsg].text) - } - private static func firstNonEmpty(_ candidates: String?...) -> String? { candidates .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } @@ -253,8 +215,6 @@ public struct NKError: Error, Equatable, Sendable { /// - responseData: Optional raw response data associated with the error. /// - kind: The kind of failure; derived from `errorCode` when `nil`. public init(errorCode: Int = 0, errorDescription: String = "", responseData: Data? = nil, kind: Kind? = nil) { - let details = errorCode == 0 ? nil : NKError.details(of: responseData) - self.errorCode = errorCode self.errorDescription = errorDescription self.error = NSError( @@ -264,10 +224,7 @@ public struct NKError: Error, Equatable, Sendable { ) self.responseData = responseData self.kind = kind ?? Kind(code: errorCode) - self.httpStatusCode = nil - self.ocsStatusCode = details?.ocsStatusCode - self.serverMessage = details?.serverMessage - self.davException = details?.davException + self.serverMessage = nil } /// Creates an `NKError` from a generic Swift `Error`. @@ -281,10 +238,7 @@ public struct NKError: Error, Equatable, Sendable { self.error = error self.responseData = responseData self.kind = Kind(error: error) - self.httpStatusCode = (error as? AFError)?.responseCode - self.ocsStatusCode = nil self.serverMessage = nil - self.davException = nil } /// Creates an `NKError` from an `NSError`. @@ -298,10 +252,7 @@ public struct NKError: Error, Equatable, Sendable { self.error = nsError self.responseData = responseData self.kind = Kind(error: nsError) - self.httpStatusCode = nil - self.ocsStatusCode = nil self.serverMessage = nil - self.davException = nil } /// Creates an `NKError` from an OCS JSON response. @@ -329,10 +280,7 @@ public struct NKError: Error, Equatable, Sendable { userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) self.kind = errorCode == 0 ? nil : Kind(code: statuscode) - self.httpStatusCode = fallbackStatusCode - self.ocsStatusCode = rootJson[.ocsMetaCode].int - self.serverMessage = NKError.serverMessage(in: rootJson) - self.davException = nil + self.serverMessage = NKError.firstNonEmpty(rootJson[.ocsDataMsg].string, rootJson[.ocsMetaMsg].string) } /// Creates an `NKError` from an HTTP status code. @@ -353,14 +301,9 @@ public struct NKError: Error, Equatable, Sendable { userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) - let details = NKError.details(of: responseData) - self.responseData = responseData self.kind = Kind(code: statusCode) - self.httpStatusCode = statusCode - self.ocsStatusCode = details.ocsStatusCode - self.serverMessage = details.serverMessage - self.davException = details.davException + self.serverMessage = nil } /// Creates an `NKError` from an HTTP response. @@ -401,11 +344,8 @@ public struct NKError: Error, Equatable, Sendable { code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) - self.kind = errorCode == 0 ? nil : Kind(code: statuscode, method: method) - self.httpStatusCode = fallbackStatusCode - self.ocsStatusCode = xml[.ocsMetaCode].int - self.serverMessage = NKError.serverMessage(in: xml) - self.davException = NKError.firstNonEmpty(xml[.davException].text) + self.kind = errorCode == 0 ? nil : Kind(davException: xml[.davException].text) ?? Kind(code: statuscode, method: method) + self.serverMessage = nil } /// Creates an `NKError` from an Alamofire response and optional Alamofire error. @@ -415,22 +355,21 @@ public struct NKError: Error, Equatable, Sendable { /// - afResponse: The Alamofire response. /// - responseData: Optional raw response data associated with the error. public init(error: AFError?, afResponse: T, responseData: Data? = nil) { + let dataResponse = afResponse as? Alamofire.DataResponse + if let errorCode = afResponse.response?.statusCode { - guard let dataResponse = afResponse as? Alamofire.DataResponse, - let errorData = dataResponse.data - else { + if let errorData = dataResponse?.data { + if let errorJson = try? JSON(data: errorData) { + self.init(rootJson: errorJson, fallbackStatusCode: errorCode, responseData: responseData) + } else { + self.init(xmlData: errorData, fallbackStatusCode: errorCode, method: dataResponse?.request?.httpMethod, responseData: responseData) + } + } else { self.init( statusCode: errorCode, fallbackDescription: Self.httpFallbackDescription(for: errorCode), responseData: responseData ) - return - } - - if let errorJson = try? JSON(data: errorData) { - self.init(rootJson: errorJson, fallbackStatusCode: errorCode, responseData: responseData) - } else { - self.init(xmlData: errorData, fallbackStatusCode: errorCode, method: dataResponse.request?.httpMethod, responseData: responseData) } } else if let error = error { @@ -456,6 +395,8 @@ public struct NKError: Error, Equatable, Sendable { } else { self.init(errorCode: 0, errorDescription: "", responseData: responseData) } + + requestPath = dataResponse?.request?.url?.path } public static func == (lhs: NKError, rhs: NKError) -> Bool { diff --git a/Sources/NextcloudKit/Utils/FileNameValidator.swift b/Sources/NextcloudKit/Utils/FileNameValidator.swift index 083b761b..100ca017 100644 --- a/Sources/NextcloudKit/Utils/FileNameValidator.swift +++ b/Sources/NextcloudKit/Utils/FileNameValidator.swift @@ -12,27 +12,27 @@ public final class FileNameValidator: Sendable { private let capabilities: NKCapabilities.Capabilities public func fileEmptyNameError() -> NKError { - NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("_file_name_empty_", value: "File name cannot be empty.", comment: "")) + NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("File name cannot be empty.", bundle: .module, comment: "Error message when a file or folder name is empty.")) } public func fileWithSpaceError() -> NKError { - NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("_file_name_validator_error_space_", value: "Name must not contain spaces at the beginning or end.", comment: "")) + NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: NSLocalizedString("Name must not contain spaces at the beginning or end.", bundle: .module, comment: "Error message when a name starts or ends with a space.")) } public func fileReservedNameError(templateString: String) -> NKError { - let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_reserved_name_", value: "\"%@\" is a forbidden name.", comment: "") + let errorMessageTemplate = NSLocalizedString("\"%@\" is a forbidden name.", bundle: .module, comment: "Error message when the server reserves a name. %@ is the name.") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } public func fileForbiddenFileExtensionError(templateString: String) -> NKError { - let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_forbidden_file_extension_", value: ".\"%@\" is a forbidden file extension.", comment: "") + let errorMessageTemplate = NSLocalizedString(".\"%@\" is a forbidden file extension.", bundle: .module, comment: "Error message when the server forbids a file extension. %@ is the extension.") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } public func fileInvalidCharacterError(templateString: String) -> NKError { - let errorMessageTemplate = NSLocalizedString("_file_name_validator_error_invalid_character_", value: "Name contains an invalid character: \"%@\".", comment: "") + let errorMessageTemplate = NSLocalizedString("Name contains an invalid character: \"%@\".", bundle: .module, comment: "Error message when a name contains a forbidden character. %@ is the character.") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index 14c565e2..a8df654b 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -1,6 +1,9 @@ { "sourceLanguage" : "en", "strings" : { + "" : { + + }, "%lld groups" : { "comment" : "Recipient summary for multiple groups.", "localizations" : { @@ -1161,196 +1164,6 @@ } } }, - "Apply permissions to all recipients?" : { - "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anvend rettighederne på alle modtagere?" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Berechtigungen auf alle Empfänger anwenden" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply permissions to all recipients?" - } - }, - "en_GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply permissions to all recipients?" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¿Aplicar permisos a todos los destinatarios?" - } - }, - "et_EE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kas rakendad õigusi kõikidele saajatele?" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appliquer les autorisations à tous les destinataires ?" - } - }, - "ga" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cuir ceadanna i bhfeidhm ar gach faighteoir?" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applicare le autorizzazioni a tutti i destinatari?" - } - }, - "pt_BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aplicar permissões a todos os destinatários?" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Применить разрешения ко всем получателям?" - } - }, - "sk_SK" : { - "stringUnit" : { - "state" : "translated", - "value" : "Použiť oprávnenia na všetkých príjemcov?" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "İzinler tüm alıcılara uygulansın mı?" - } - }, - "zh_HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "要將權限套用至所有接收者嗎?" - } - }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "套用權限至所有收件人?" - } - } - } - }, - "Apply to all" : { - "comment" : "Button that confirms applying top-level permissions to every recipient.", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anvend på alle" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf alle anwenden" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply to all" - } - }, - "en_GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply to all" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aplicar a todos" - } - }, - "et_EE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rakenda kõigile" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appliquer à tous" - } - }, - "ga" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cuir i bhfeidhm ar gach rud" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applicare a tutti" - } - }, - "pt_BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aplicar a todos" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Применить ко всем" - } - }, - "sk_SK" : { - "stringUnit" : { - "state" : "translated", - "value" : "Použiť na všetko" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tümüne uygula" - } - }, - "zh_HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "套用至全部" - } - }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "套用至全部" - } - } - } - }, "Anyone" : { "comment" : "Audience option that makes a unified share accessible to anyone with its public link.", "localizations" : { @@ -1566,99 +1379,289 @@ } } }, - "Cancel" : { - "comment" : "Button that dismisses a confirmation dialog without making changes.", + "Apply permissions to all recipients?" : { + "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", "localizations" : { - "af" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Anvend rettighederne på alle modtagere?" } }, - "an" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Berechtigungen auf alle Empfänger anwenden" } }, - "ar" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Apply permissions to all recipients?" } }, - "ast" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Apply permissions to all recipients?" } }, - "az" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "¿Aplicar permisos a todos los destinatarios?" } }, - "be" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Kas rakendad õigusi kõikidele saajatele?" } }, - "bg_BG" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Appliquer les autorisations à tous les destinataires ?" } }, - "bn_BD" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Cuir ceadanna i bhfeidhm ar gach faighteoir?" } }, - "br" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Applicare le autorizzazioni a tutti i destinatari?" } }, - "bs" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "Aplicar permissões a todos os destinatários?" } }, - "ca" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel·la" + "value" : "Применить разрешения ко всем получателям?" } }, - "cs_CZ" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Zrušit" + "value" : "Použiť oprávnenia na všetkých príjemcov?" } }, - "cy_GB" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Cancel" + "value" : "İzinler tüm alıcılara uygulansın mı?" } }, - "da" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Annuller" + "value" : "要將權限套用至所有接收者嗎?" } }, - "de" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Abbrechen" + "value" : "套用權限至所有收件人?" } - }, + } + } + }, + "Apply to all" : { + "comment" : "Button that confirms applying top-level permissions to every recipient.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anvend på alle" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auf alle anwenden" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apply to all" + } + }, + "en_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apply to all" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aplicar a todos" + } + }, + "et_EE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rakenda kõigile" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appliquer à tous" + } + }, + "ga" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cuir i bhfeidhm ar gach rud" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Applicare a tutti" + } + }, + "pt_BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aplicar a todos" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Применить ко всем" + } + }, + "sk_SK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Použiť na všetko" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tümüne uygula" + } + }, + "zh_HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "套用至全部" + } + }, + "zh_TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "套用至全部" + } + } + } + }, + "Cancel" : { + "comment" : "Button that dismisses a confirmation dialog without making changes.", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "an" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "ast" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "bg_BG" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "bn_BD" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "br" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "bs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel·la" + } + }, + "cs_CZ" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zrušit" + } + }, + "cy_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annuller" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abbrechen" + } + }, "el" : { "stringUnit" : { "state" : "translated", @@ -2207,1669 +2210,1669 @@ } } }, - "Custom permissions" : { - "comment" : "Menu option that reveals per-permission toggles for custom permissions.", + "Copied" : { + "comment" : "Temporary button label confirming that a share link was copied.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أذونات مخصصة" + "value" : "نُسخت " } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Personlizar los permisos" - } - }, - "az" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xüsusi icazələr" + "value" : "Copióse" } }, - "bg_BG" : { + "be" : { "stringUnit" : { "state" : "translated", - "value" : "Персонализиране на права" + "value" : "Скапіявана" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalitzats" + "value" : "Copiat" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Uživatelsky určená oprávnění" + "value" : "Zkopírováno" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brugerdefinerede rettigheder" + "value" : "Kopieret" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzerdefinierte Berechtigungen" + "value" : "Kopiert" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προσαρμοσμένα δικαιώματα" + "value" : "Αντιγράφηκε" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "Copied" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "Copied" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Copiado" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Copiado" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Copiado" + } + }, + "es_PE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiado" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kohandatud õigused" + "value" : "Kopeeritud" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Baimen pertsonalizatuak" + "value" : "Kopiatuta" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "مجوزهای سفارشی" + "value" : "کپی شد" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Mukautetut oikeudet" + "value" : "Kopioitu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autorisations personnalisées" + "value" : "Copié" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Ceadanna saincheaptha" + "value" : "Cóipeáladh" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Copiado" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Prilagođena dopuštenja" + "value" : "Kopirano" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Egyéni jogosultságok" + "value" : "Másolva" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Izin kustom" + "value" : "Disalin" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Sérsniðnar heimildir" + "value" : "Afritað" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Autorizzazioni personalizzate" + "value" : "Copiato" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "カスタム権限" + "value" : "コピー" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "Copied" } }, - "ko" : { + "kab" : { "stringUnit" : { "state" : "translated", - "value" : "사용자 지정 권한" + "value" : "Yettwanɣel" } }, - "lo" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "ສິດອະນຸຍາດແບບກຳນົດເອງ" + "value" : "복사됨" } }, - "lt_LT" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Tinkinti leidimai" + "value" : "Copied" } }, - "mk" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Прилагодени дозволи" + "value" : "Nukopijuota" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Тусгай эрхүүд" + "value" : "Хуулагдсан" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Egendefinerte tillatelser" + "value" : "Kopiert" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aangepaste machtigingen" + "value" : "Gekopieerd" + } + }, + "oc" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Uprawnienia niestandardowe" + "value" : "Skopiowane" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Permissões personalizadas" + "value" : "Copiado" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Permissões personalizadas" + "value" : "Copiado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Permisiuni particularizate" + "value" : "Copiat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пользовательские права доступа" + "value" : "Скопировано" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Permissos personalizados" + "value" : "Copiadu" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Vlastné oprávnenia" + "value" : "Skopírované" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Dovoljenja po meri" + "value" : "Kopirano" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Произвољне дозволе" + "value" : "Копирано" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Anpassade behörigheter" + "value" : "Kopierat" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Ruhusa maalum" + "value" : "Iliyonakiliwa" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "நகலெடுக்கப்பட்டது" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Özel izinler" + "value" : "Kopyalandı" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "ئىختىيارى ئىجازەت" + "value" : "كۆچۈرۈلگەن" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Спеціальні дозволи" + "value" : "Скопійовано" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Quyền tùy chỉnh" + "value" : "Đã sao chép" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "自定义权限" + "value" : "已复制" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "自訂權限" + "value" : "已複製" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "自訂權限" + "value" : "已複製" } } } }, - "Copied" : { - "comment" : "Temporary button label confirming that a share link was copied.", + "Copy private link" : { + "comment" : "Copies the in-server link of the file for invited people.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نُسخت " + "value" : "نسخ الرابط الخاص" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Copióse" + "value" : "Copiar l'enllaz priváu" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Скапіявана" + "value" : "Капіяваць прыватную спасылку" + } + }, + "bg_BG" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копирай скритата връзка" + } + }, + "br" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eilañ al liamm prevez" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Copia l'enllaç privat" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírováno" + "value" : "Zkopírovat soukromý odkaz" + } + }, + "cy_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copïo dolen breifat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieret" + "value" : "Kopiér privat link" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiert" + "value" : "Privaten Link kopieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αντιγράφηκε" + "value" : "Αντιγραφή ιδιωτικού συνδέσμου" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Copy private link" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Copy private link" } }, - "es" : { + "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Kopii privatan ligilon" } }, - "es_EC" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar enlace privado" } }, - "es_MX" : { + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar enlace privado" } }, - "es_PE" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar enlace privado" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeritud" + "value" : "Kopeeri privaatne link" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatuta" + "value" : "Kopiatu esteka pribatua" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "کپی شد" + "value" : "کپی پیوند خصوصی" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioitu" + "value" : "Kopioi yksityinen linkki" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copié" + "value" : "Copier le lien privé" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáladh" + "value" : "Cóipeáil nasc príobháideach" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar a ligazón privada" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "העתקת קישור פרטי" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopirano" + "value" : "Kopiraj privatnu poveznicu" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Másolva" + "value" : "Személyes hivatkozás másolása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Disalin" + "value" : "Salin tautan privat" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Afritað" + "value" : "Afrita einkatengil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copiato" + "value" : "Copia collegamento privato" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "コピー" + "value" : "プライベートリンクをコピー" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Copy private link" } }, - "kab" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Yettwanɣel" + "value" : "개인 링크 주소 복사" } }, - "ko" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "복사됨" + "value" : "Copy private link" } }, - "lo" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Kopijuoti privačią nuorodą" } }, - "lt_LT" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Nukopijuota" + "value" : "Копирај приватен линк" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Хуулагдсан" + "value" : "Хувийн холбоос хуулах" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiert" + "value" : "Kopier privat lenke" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gekopieerd" + "value" : "Privélink kopiëren" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Copiar lo ligam privat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skopiowane" + "value" : "Kopiuj link prywatny" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar link privado" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Copiar link privado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Copiază link privat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировано" + "value" : "Скопировать закрытую ссылку" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiadu" + "value" : "Còpia ligòngiu privadu" + } + }, + "si" : { + "stringUnit" : { + "state" : "translated", + "value" : "පුද්. සබැඳිය පිටපත් කරන්න" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Skopírované" + "value" : "Kopírovať privátny odkaz" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopirano" + "value" : "Kopiraj zasebno povezavo" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Копирано" + "value" : "Копирај приватну везу" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kopierat" + "value" : "Kopiera privat länk" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Iliyonakiliwa" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "நகலெடுக்கப்பட்டது" + "value" : "Nakili kiungio binafsi" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopyalandı" + "value" : "Kişisel bağlantıyı kopyala" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "كۆچۈرۈلگەن" + "value" : "شەخسىي ئۇلىنىشنى كۆچۈرۈڭ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Скопійовано" + "value" : "Копіювати приватне посилання" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Đã sao chép" + "value" : "Sao chép liên kết riêng tư" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "已复制" + "value" : "复制私有链接" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "已複製" + "value" : "複製私人連結" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "已複製" + "value" : "複製私人連結" } } } }, - "Copy private link" : { - "comment" : "Copies the in-server link of the file for invited people.", + "Copy public link" : { + "comment" : "Copies the public link of an Anyone share.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نسخ الرابط الخاص" + "value" : "نسخ الرابط العمومي" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar l'enllaz priváu" + "value" : "Copiar l'enllaz públicu" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Капіяваць прыватную спасылку" + "value" : "Скапіяваць публічную спасылку" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Копирай скритата връзка" + "value" : "Копирай публичната връзка" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Eilañ al liamm prevez" + "value" : "Eilañ al liamm foran" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Copia l'enllaç privat" + "value" : "Copia l'enllaç públic" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírovat soukromý odkaz" + "value" : "Zkopírovat veřejný odkaz" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copïo dolen breifat" + "value" : "Copïo dolen gyhoeddus" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiér privat link" + "value" : "Kopier offentligt link" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Privaten Link kopieren" + "value" : "Öffentlichen Link kopieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αντιγραφή ιδιωτικού συνδέσμου" + "value" : "Αντιγραφή δημόσιου συνδέσμου" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copy public link" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copy public link" } }, - "eo" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Kopii privatan ligilon" + "value" : "Copiar enlace público" } }, - "es" : { + "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiar link publico" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiar enlace público" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiar enlace público" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeri privaatne link" + "value" : "Kopeeri avalik link" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatu esteka pribatua" + "value" : "Kopiatu esteka publikoa" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "کپی پیوند خصوصی" + "value" : "کپی لینک عمومی" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioi yksityinen linkki" + "value" : "Kopioi julkinen linkki" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier le lien privé" + "value" : "Copier le lien public" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáil nasc príobháideach" + "value" : "Cóipeáil nasc poiblí" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar a ligazón privada" + "value" : "Copiar a ligazón pública" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "העתקת קישור פרטי" + "value" : "העתקת הקישור הציבורי" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj privatnu poveznicu" + "value" : "Kopiraj javnu poveznicu" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Személyes hivatkozás másolása" + "value" : "Nyilvános hivatkozás másolása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Salin tautan privat" + "value" : "Salin tautan publik" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Afrita einkatengil" + "value" : "Afrita opinberan tengil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia collegamento privato" + "value" : "Copia collegamento pubblico" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "プライベートリンクをコピー" + "value" : "公開リンクをコピー" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copy public link" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "개인 링크 주소 복사" + "value" : "공개 링크 복사" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "ສຳເນົາລິ້ງສາທາລະນະ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Kopijuoti privačią nuorodą" + "value" : "Kopijuoti viešąją nuorodą" + } + }, + "lv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ievietot publisko saiti starpliktuvē" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај приватен линк" + "value" : "Копирај јавен линк" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Хувийн холбоос хуулах" + "value" : "Нийтийн холбоос хуулах" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier privat lenke" + "value" : "Kopier offentlig lenke" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privélink kopiëren" + "value" : "Kopieer openbare link" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar lo ligam privat" + "value" : "Copiar lo ligam public" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj link prywatny" + "value" : "Kopiuj link publiczny" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link privado" + "value" : "Copiar link público" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link privado" + "value" : "Copiar ligação publica" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Copiază link privat" + "value" : "Copiză link-ul public" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировать закрытую ссылку" + "value" : "Скопировать общедоступную ссылку" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Còpia ligòngiu privadu" - } - }, - "si" : { - "stringUnit" : { - "state" : "translated", - "value" : "පුද්. සබැඳිය පිටපත් කරන්න" + "value" : "Còpia ligòngiu pùblicu" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Kopírovať privátny odkaz" + "value" : "Kopírovať verejný odkaz" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj zasebno povezavo" + "value" : "Kopiraj javno povezavo" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај приватну везу" + "value" : "Копирај јавни линк" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiera privat länk" + "value" : "Kopiera offentlig länk" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Nakili kiungio binafsi" + "value" : "Nakili kiungo cha umma" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kişisel bağlantıyı kopyala" + "value" : "Herkese açık bağlantıyı kopyala" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "شەخسىي ئۇلىنىشنى كۆچۈرۈڭ" + "value" : "ئاممىۋى ئۇلىنىشنى كۆچۈرۈڭ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Копіювати приватне посилання" + "value" : "Копіювати публічне посилання" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umumiy havoladan nusxa oling" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sao chép liên kết riêng tư" + "value" : "Sao chép liên kết công khai" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "复制私有链接" + "value" : "复制公开链接" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "複製私人連結" + "value" : "複製公開連結" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "複製私人連結" + "value" : "複製公開連結" } } } }, - "Copy public link" : { - "comment" : "Copies the public link of an Anyone share.", + "Could not create share, try again later" : { + "comment" : "Error message shown when a unified share cannot be created.", "localizations" : { - "ar" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "نسخ الرابط العمومي" + "value" : "Delingen kunne ikke oprettes. Prøv igen senere." } }, - "ast" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar l'enllaz públicu" + "value" : "Freigabe konnte nicht erstellt werden. Bitte später erneut versuchen." } }, - "be" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Скапіяваць публічную спасылку" + "value" : "Could not create share, try again later" } }, - "bg_BG" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Копирай публичната връзка" + "value" : "Could not create share, try again later" } }, - "br" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eilañ al liamm foran" + "value" : "No se ha podido compartir, inténtalo de nuevo más tarde" } }, - "ca" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Copia l'enllaç públic" + "value" : "Jagamise lisamine ei õnnestunud. Palun proovi hiljem uuesti." } }, - "cs_CZ" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírovat veřejný odkaz" + "value" : "Impossible de créer le partage, réessayez plus tard" } }, - "cy_GB" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Copïo dolen gyhoeddus" + "value" : "Níorbh fhéidir an comhroinnt a chruthú; déan iarracht arís níos déanaí" } }, - "da" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier offentligt link" + "value" : "Impossibile creare la condivisione; riprova più tardi." } }, - "de" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Öffentlichen Link kopieren" + "value" : "Não foi possível criar o compartilhamento; tente novamente mais tarde" } }, - "el" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Αντιγραφή δημόσιου συνδέσμου" + "value" : "Не удалось создать общий ресурс, повторите попытку позже" } }, - "en" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Copy public link" + "value" : "Nepodarilo sa vytvoriť zdieľanie, skúste to znova neskôr" } }, - "en_GB" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Copy public link" + "value" : "Paylaşım oluşturulamadı, bir süre sonra yeniden deneyin" } }, - "es" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "無法建立分享,請稍後再試" } }, - "es_AR" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link publico" + "value" : "無法建立分享,請稍後再試" } - }, - "es_EC" : { + } + } + }, + "Couldn't load shares" : { + "comment" : "Full-screen error title when the share list fails to load.", + "localizations" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "Delingerne kunne ikke indlæses" } }, - "es_MX" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "Freigaben konnten nicht geladen werden" } }, - "et_EE" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeri avalik link" + "value" : "Couldn't load shares" } }, - "eu" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatu esteka publikoa" + "value" : "Couldn't load shares" } }, - "fa" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "کپی لینک عمومی" + "value" : "Error al cargar" } }, - "fi_FI" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioi julkinen linkki" + "value" : "Jagamiste laadimine ei õnnestunud" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier le lien public" + "value" : "Impossible de charger les partages" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáil nasc poiblí" + "value" : "Níorbh fhéidir scaireanna a luchtú" } }, - "gl" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar a ligazón pública" + "value" : "Impossibile caricare le condivisioni" } }, - "he" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "העתקת הקישור הציבורי" + "value" : "Não foi possível carregar os compartilhamentos" } }, - "hr" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj javnu poveznicu" + "value" : "Не удалось загрузить список общих ресурсов" } }, - "hu_HU" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Nyilvános hivatkozás másolása" + "value" : "Nepodarilo sa načítať zdieľania" } }, - "id" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Salin tautan publik" + "value" : "Paylaşımlar yüklenemedi" } }, - "is" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Afrita opinberan tengil" + "value" : "無法載入分享" } }, - "it" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Copia collegamento pubblico" + "value" : "無法載入分享" } - }, - "ja_JP" : { - "stringUnit" : { - "state" : "translated", - "value" : "公開リンクをコピー" - } - }, - "ka" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copy public link" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "공개 링크 복사" - } - }, - "lo" : { + } + } + }, + "Custom link" : { + "comment" : "Heading of the editable public-link token row.", + "localizations" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "ສຳເນົາລິ້ງສາທາລະນະ" + "value" : "Tilpasset link" } }, - "lt_LT" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopijuoti viešąją nuorodą" + "value" : "Benutzerdefinierter Link" } }, - "lv" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ievietot publisko saiti starpliktuvē" + "value" : "Custom link" } }, - "mk" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај јавен линк" + "value" : "Custom link" } }, - "mn" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Нийтийн холбоос хуулах" + "value" : "Personalizar enlace" } }, - "nb_NO" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier offentlig lenke" + "value" : "Kohendatud link" } }, - "nl" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieer openbare link" + "value" : "Lien personnalisé" } }, - "oc" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar lo ligam public" + "value" : "Nasc saincheaptha" } }, - "pl" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj link publiczny" + "value" : "Link personalizzato" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link público" - } - }, - "pt_PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copiar ligação publica" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copiză link-ul public" + "value" : "Link personalizado" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировать общедоступную ссылку" - } - }, - "sc" : { - "stringUnit" : { - "state" : "translated", - "value" : "Còpia ligòngiu pùblicu" + "value" : "Пользовательская ссылка" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Kopírovať verejný odkaz" + "value" : "Vlastný odkaz" } }, - "sl" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj javno povezavo" + "value" : "Özel bağlantı" } }, - "sr" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај јавни линк" + "value" : "自訂連結" } }, - "sv" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiera offentlig länk" + "value" : "自訂連結" } - }, - "sw" : { + } + } + }, + "Custom permissions" : { + "comment" : "Menu option that reveals per-permission toggles for custom permissions.", + "localizations" : { + "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Nakili kiungo cha umma" + "value" : "أذونات مخصصة" } }, - "tr" : { + "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Herkese açık bağlantıyı kopyala" + "value" : "Personlizar los permisos" } }, - "ug" : { + "az" : { "stringUnit" : { "state" : "translated", - "value" : "ئاممىۋى ئۇلىنىشنى كۆچۈرۈڭ" + "value" : "Xüsusi icazələr" } }, - "uk" : { + "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Копіювати публічне посилання" + "value" : "Персонализиране на права" } }, - "uz" : { + "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Umumiy havoladan nusxa oling" + "value" : "Permisos personalitzats" } }, - "vi" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Sao chép liên kết công khai" + "value" : "Uživatelsky určená oprávnění" } }, - "zh_CN" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "复制公开链接" + "value" : "Brugerdefinerede rettigheder" } }, - "zh_HK" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "複製公開連結" + "value" : "Benutzerdefinierte Berechtigungen" } }, - "zh_TW" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "複製公開連結" + "value" : "Προσαρμοσμένα δικαιώματα" } - } - } - }, - "Could not create share, try again later" : { - "comment" : "Error message shown when a unified share cannot be created.", - "localizations" : { - "da" : { + }, + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Delingen kunne ikke oprettes. Prøv igen senere." + "value" : "Custom permissions" } }, - "de" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Freigabe konnte nicht erstellt werden. Bitte später erneut versuchen." + "value" : "Custom permissions" } }, - "en" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Could not create share, try again later" + "value" : "Permisos personalizados" } }, - "en_GB" : { + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Could not create share, try again later" + "value" : "Permisos personalizados" } }, - "es" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "No se ha podido compartir, inténtalo de nuevo más tarde" + "value" : "Permisos personalizados" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Jagamise lisamine ei õnnestunud. Palun proovi hiljem uuesti." + "value" : "Kohandatud õigused" } }, - "fr" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de créer le partage, réessayez plus tard" + "value" : "Baimen pertsonalizatuak" } }, - "ga" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Níorbh fhéidir an comhroinnt a chruthú; déan iarracht arís níos déanaí" + "value" : "مجوزهای سفارشی" } }, - "it" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile creare la condivisione; riprova più tardi." + "value" : "Mukautetut oikeudet" } }, - "pt_BR" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível criar o compartilhamento; tente novamente mais tarde" + "value" : "Autorisations personnalisées" } }, - "ru" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось создать общий ресурс, повторите попытку позже" + "value" : "Ceadanna saincheaptha" } }, - "sk_SK" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Nepodarilo sa vytvoriť zdieľanie, skúste to znova neskôr" + "value" : "Permisos personalizados" } }, - "tr" : { + "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Paylaşım oluşturulamadı, bir süre sonra yeniden deneyin" + "value" : "Prilagođena dopuštenja" } }, - "zh_HK" : { + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "無法建立分享,請稍後再試" + "value" : "Egyéni jogosultságok" } }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "無法建立分享,請稍後再試" - } - } - } - }, - "Couldn't load shares" : { - "comment" : "Full-screen error title when the share list fails to load.", - "localizations" : { - "da" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "Delingerne kunne ikke indlæses" + "value" : "Izin kustom" } }, - "de" : { + "is" : { "stringUnit" : { "state" : "translated", - "value" : "Freigaben konnten nicht geladen werden" + "value" : "Sérsniðnar heimildir" } }, - "en" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Couldn't load shares" + "value" : "Autorizzazioni personalizzate" } }, - "en_GB" : { + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Couldn't load shares" + "value" : "カスタム権限" } }, - "es" : { + "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Error al cargar" + "value" : "Custom permissions" } }, - "et_EE" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Jagamiste laadimine ei õnnestunud" + "value" : "사용자 지정 권한" } }, - "fr" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de charger les partages" + "value" : "ສິດອະນຸຍາດແບບກຳນົດເອງ" } }, - "ga" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Níorbh fhéidir scaireanna a luchtú" + "value" : "Tinkinti leidimai" } }, - "it" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile caricare le condivisioni" + "value" : "Прилагодени дозволи" } }, - "pt_BR" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível carregar os compartilhamentos" + "value" : "Тусгай эрхүүд" } }, - "ru" : { + "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось загрузить список общих ресурсов" + "value" : "Egendefinerte tillatelser" } }, - "sk_SK" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nepodarilo sa načítať zdieľania" + "value" : "Aangepaste machtigingen" } }, - "tr" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Paylaşımlar yüklenemedi" + "value" : "Uprawnienia niestandardowe" } }, - "zh_HK" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "無法載入分享" + "value" : "Permissões personalizadas" } }, - "zh_TW" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "無法載入分享" + "value" : "Permissões personalizadas" } - } - } - }, - "Custom link" : { - "comment" : "Heading of the editable public-link token row.", - "localizations" : { - "da" : { + }, + "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Tilpasset link" + "value" : "Permisiuni particularizate" } }, - "de" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzerdefinierter Link" + "value" : "Пользовательские права доступа" } }, - "en" : { + "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Custom link" + "value" : "Permissos personalizados" } }, - "en_GB" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Custom link" + "value" : "Vlastné oprávnenia" } }, - "es" : { + "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Personalizar enlace" + "value" : "Dovoljenja po meri" } }, - "et_EE" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Kohendatud link" + "value" : "Произвољне дозволе" } }, - "fr" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Lien personnalisé" + "value" : "Anpassade behörigheter" } }, - "ga" : { + "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Nasc saincheaptha" + "value" : "Ruhusa maalum" } }, - "it" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Link personalizzato" + "value" : "Özel izinler" } }, - "pt_BR" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Link personalizado" + "value" : "ئىختىيارى ئىجازەت" } }, - "ru" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Пользовательская ссылка" + "value" : "Спеціальні дозволи" } }, - "sk_SK" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Vlastný odkaz" + "value" : "Quyền tùy chỉnh" } }, - "tr" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Özel bağlantı" + "value" : "自定义权限" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "自訂連結" + "value" : "自訂權限" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "自訂連結" + "value" : "自訂權限" } } } @@ -5107,6 +5110,7 @@ }, "Edit" : { "comment" : "Action that opens the unified share editor.", + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5775,342 +5779,342 @@ } } }, - "Hide recipients" : { - "comment" : "Accessibility label for collapsing a share's recipient list.", + "For people who already have access" : { + "comment" : "Subtitle explaining that an internal link works only for people who already have access.", "localizations" : { - "da" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Skjul modtagere" + "value" : "Pro lidi, kteří už mají přístup" } }, - "de" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Empfänger ausblenden" + "value" : "For personer, der allerede har adgang" } }, - "en" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hide recipients" + "value" : "Für Personen, die bereits Zugriff haben" } }, - "en_GB" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "Hide recipients" + "value" : "Για άτομα που έχουν ήδη πρόσβαση" } }, - "es" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Esconder destinatarios" + "value" : "For people who already have access" } }, - "et_EE" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Peida saajad" + "value" : "For people who already have access" } }, - "fr" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Masquer les destinataires" + "value" : "Para las personas que ya tienen acceso" } }, - "ga" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Folaigh na faighteoirí" + "value" : "Neile, kellel juba on ligipääs" } }, - "it" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Nascondi destinatari" + "value" : "Dagoeneko sarbidea duten pertsonentzat" } }, - "pt_BR" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Ocultar destinatários" + "value" : "برای افرادی که قبلاً دسترسی دارند" } }, - "ru" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Скрыть получателей" + "value" : "Henkilöille, joilla on jo käyttöoikeus" } }, - "sk_SK" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Skryť príjemcov" + "value" : "Pour les personnes qui y ont déjà accès" } }, - "tr" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Alıcıları gizle" + "value" : "Do dhaoine a bhfuil rochtain acu cheana féin" } }, - "zh_HK" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "隱藏接收者" + "value" : "Para persoas que xa teñen acceso" } }, - "zh_TW" : { + "hr" : { "stringUnit" : { "state" : "translated", - "value" : "隱藏收件人" + "value" : "Za osobe koje već imaju pristup" } - } - } - }, - "For people who already have access" : { - "comment" : "Subtitle explaining that an internal link works only for people who already have access.", - "localizations" : { - "cs_CZ" : { + }, + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Pro lidi, kteří už mají přístup" + "value" : "Azok számára, akik már rendelkeznek hozzáféréssel" } }, - "da" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "For personer, der allerede har adgang" + "value" : "Untuk orang yang sudah memiliki akses" } }, - "de" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Für Personen, die bereits Zugriff haben" + "value" : "Per le persone che hanno già accesso" } }, - "el" : { + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Για άτομα που έχουν ήδη πρόσβαση" + "value" : "すでにアクセス権を持っている人向け" } }, - "en" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "For people who already have access" + "value" : "이미 접근 권한이 있는 사람들에게" } }, - "en_GB" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "For people who already have access" + "value" : "ສຳລັບຄົນທີ່ມີສິດເຂົ້າເຖິງຢູ່ແລ້ວ" } }, - "es" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Para las personas que ya tienen acceso" + "value" : "Tiems, kurie jau turi prieigą" } }, - "et_EE" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Neile, kellel juba on ligipääs" + "value" : "За луѓе кој веќе имаат пристап" } }, - "eu" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Dagoeneko sarbidea duten pertsonentzat" + "value" : "Аль хэдийн хандах эрхтэй хүмүүст" } }, - "fa" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "برای افرادی که قبلاً دسترسی دارند" + "value" : "Voor personen die al toegang hebben" } }, - "fi_FI" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Henkilöille, joilla on jo käyttöoikeus" + "value" : "Dla osób, które już mają dostęp" } }, - "fr" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Pour les personnes qui y ont déjà accès" + "value" : "Para pessoas que já têm acesso" } }, - "ga" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Do dhaoine a bhfuil rochtain acu cheana féin" + "value" : "Para as pessoas que já têm acesso" } }, - "gl" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Para persoas que xa teñen acceso" + "value" : "Для тех, у кого уже есть доступ" } }, - "hr" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Za osobe koje već imaju pristup" + "value" : "Pre ľudí, ktorí už majú prístup" } }, - "hu_HU" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Azok számára, akik már rendelkeznek hozzáféréssel" + "value" : "Особе које већ имају приступ" } }, - "id" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Untuk orang yang sudah memiliki akses" + "value" : "För personer som redan har åtkomst" } }, - "it" : { + "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Per le persone che hanno già accesso" + "value" : "Kwa watu ambao tayari wana ufikiaji" } }, - "ja_JP" : { + "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "すでにアクセス権を持っている人向け" + "value" : "สำหรับผู้ที่มีสิทธิ์เข้าถึงอยู่แล้ว" } }, - "ko" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "이미 접근 권한이 있는 사람들에게" + "value" : "Erişme izni olan kişiler için" } }, - "lo" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "ສຳລັບຄົນທີ່ມີສິດເຂົ້າເຖິງຢູ່ແລ້ວ" + "value" : "ئاللىبۇرۇن كىرەلەيدىغان ئادەملەر ئۈچۈن" } }, - "lt_LT" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Tiems, kurie jau turi prieigą" + "value" : "Для тих, хто має доступ" } }, - "mk" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "За луѓе кој веќе имаат пристап" + "value" : "对于已经拥有访问权限的人员" } }, - "mn" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Аль хэдийн хандах эрхтэй хүмүүст" + "value" : "對於已有存取權限的人" } }, - "nl" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Voor personen die al toegang hebben" + "value" : "對於已有存取權限的人" } - }, - "pl" : { + } + } + }, + "Hide recipients" : { + "comment" : "Accessibility label for collapsing a share's recipient list.", + "localizations" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Dla osób, które już mają dostęp" + "value" : "Skjul modtagere" } }, - "pt_BR" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Para pessoas que já têm acesso" + "value" : "Empfänger ausblenden" } }, - "pt_PT" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Para as pessoas que já têm acesso" + "value" : "Hide recipients" } }, - "ru" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Для тех, у кого уже есть доступ" + "value" : "Hide recipients" } }, - "sk_SK" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Pre ľudí, ktorí už majú prístup" + "value" : "Esconder destinatarios" } }, - "sr" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Особе које већ имају приступ" + "value" : "Peida saajad" } }, - "sv" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "För personer som redan har åtkomst" + "value" : "Masquer les destinataires" } }, - "sw" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Kwa watu ambao tayari wana ufikiaji" + "value" : "Folaigh na faighteoirí" } }, - "th_TH" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "สำหรับผู้ที่มีสิทธิ์เข้าถึงอยู่แล้ว" + "value" : "Nascondi destinatari" } }, - "tr" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Erişme izni olan kişiler için" + "value" : "Ocultar destinatários" } }, - "ug" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "ئاللىبۇرۇن كىرەلەيدىغان ئادەملەر ئۈچۈن" + "value" : "Скрыть получателей" } }, - "uk" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Для тих, хто має доступ" + "value" : "Skryť príjemcov" } }, - "zh_CN" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "对于已经拥有访问权限的人员" + "value" : "Alıcıları gizle" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "對於已有存取權限的人" + "value" : "隱藏接收者" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "對於已有存取權限的人" + "value" : "隱藏收件人" } } } @@ -7075,7 +7079,7 @@ } }, "OK" : { - "comment" : "Button that dismisses an error alert.", + "comment" : "Button label for error alert dismissal.", "localizations" : { "da" : { "stringUnit" : { @@ -7731,6 +7735,101 @@ } } }, + "Remove password" : { + "comment" : "Destructive action that removes the password from a share.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fjern adgangskode" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort entfernen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove password" + } + }, + "en_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove password" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eliminar contraseña" + } + }, + "et_EE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eemalda salaõna" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer le mot de passe" + } + }, + "ga" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bain an focal faire" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rimuovi la password" + } + }, + "pt_BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover senha" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалить пароль" + } + }, + "sk_SK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odstrániť heslo" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolayı kaldır" + } + }, + "zh_HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除密碼" + } + }, + "zh_TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除密碼" + } + } + } + }, "Retry" : { "comment" : "Button that retries loading the unified share list after an error.", "localizations" : { @@ -8187,2706 +8286,2612 @@ "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Спробувати ще раз" + "value" : "Спробувати ще раз" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qayta urinish" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thử lại" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "重试" + } + }, + "zh_HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "重試" + } + }, + "zh_TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重試" + } + } + } + }, + "Save" : { + "comment" : "Button that confirms a text property change.", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "an" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "ast" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "bg_BG" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "bn_BD" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, - "uz" : { + "br" : { "stringUnit" : { "state" : "translated", - "value" : "Qayta urinish" + "value" : "Save" } }, - "vi" : { + "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Thử lại" + "value" : "Save" } }, - "zh_CN" : { + "ca" : { "stringUnit" : { "state" : "translated", - "value" : "重试" + "value" : "Desa" } }, - "zh_HK" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "重試" + "value" : "Uložit" } }, - "zh_TW" : { + "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "重試" + "value" : "Save" } - } - } - }, - "Scan QR Code" : { - "comment" : "Button label\nNavigation bar title", - "localizations" : { + }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR-kode" + "value" : "Gem" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "QR-Code scannen" + "value" : "Speichern" } }, - "en" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR Code" + "value" : "Αποθήκευση" } }, - "en_GB" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR Code" + "value" : "Save" } }, - "es" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Escanear código QR" + "value" : "Save" } }, - "et_EE" : { + "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Skaneeri QR-koodi" + "value" : "Save" } }, - "fr" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Scanner le QR code" + "value" : "Guardar" } }, - "ga" : { + "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Scanáil Cód QR" + "value" : "Save" } }, - "it" : { + "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Scansiona il QR Code" + "value" : "Save" } }, - "mk" : { + "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Скенирај QR код" + "value" : "Save" } }, - "pt_BR" : { + "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Escanear código QR" + "value" : "Save" } }, - "ru" : { + "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Сканировать QR код" + "value" : "Save" } }, - "sk_SK" : { + "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Naskenovať QR kód" + "value" : "Save" } }, - "tr" : { + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Kare kodu tara" + "value" : "Save" } }, - "zh_HK" : { + "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "掃描 QR 碼" + "value" : "Save" } }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "掃描 QR code" - } - } - } - }, - "Send" : { - "comment" : "Primary action button title in the unified share view.", - "localizations" : { - "af" : { + "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Stuur" + "value" : "Save" } }, - "ar" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "أرسِل" + "value" : "Save" } }, - "ast" : { + "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Unviar" + "value" : "Save" } }, - "az" : { + "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Göndər" + "value" : "Save" } }, - "be" : { + "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Адправіць" + "value" : "Save" } }, - "bg_BG" : { + "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Изпращане" + "value" : "Save" } }, - "bn_BD" : { + "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "পাঠাও" + "value" : "Save" } }, - "br" : { + "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Kas" + "value" : "Save" } }, - "bs" : { + "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji" + "value" : "Save" } }, - "ca" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Envia" + "value" : "Salvesta" } }, - "cs_CZ" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Odeslat" + "value" : "Save" } }, - "cy_GB" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Anfon" + "value" : "ذخیره" } }, - "da" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Save" } }, - "de" : { + "fo" : { "stringUnit" : { "state" : "translated", - "value" : "Senden" + "value" : "Save" } }, - "el" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Αποστολή" + "value" : "Enregistrer" } }, - "en" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Sábháil" } }, - "en_GB" : { + "gd" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Save" } }, - "eo" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Sendi" + "value" : "Gardar" } }, - "es" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_419" : { + "hi_IN" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_AR" : { + "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Spremi" } }, - "es_CL" : { + "hsb" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_CO" : { + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Mentés" } }, - "es_CR" : { + "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_DO" : { + "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_EC" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Simpan" } }, - "es_GT" : { + "ig" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_HN" : { + "is" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Vista" } }, - "es_MX" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_NI" : { + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "保存する" } }, - "es_PA" : { + "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_PE" : { + "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_PR" : { + "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Sekles" } }, - "es_PY" : { + "km" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_SV" : { + "kn" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "es_UY" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "et_EE" : { + "la" : { "stringUnit" : { "state" : "translated", - "value" : "Saada" + "value" : "Save" } }, - "eu" : { + "lb" : { "stringUnit" : { "state" : "translated", - "value" : "Bidali" + "value" : "Save" } }, - "fa" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ارسال" + "value" : "Save" } }, - "fi_FI" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Lähetä" + "value" : "Išsaugoti" } }, - "fr" : { + "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer" + "value" : "Save" } }, - "ga" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Seol" + "value" : "Save" } }, - "gd" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Cuir" + "value" : "–•–∞–¥–≥–∞–ª–∞—Ö" } }, - "gl" : { + "mr" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "he" : { + "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "שליחה" + "value" : "Save" } }, - "hr" : { + "my" : { "stringUnit" : { "state" : "translated", - "value" : "Pošaljite" + "value" : "Save" } }, - "hu_HU" : { + "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Küldés" + "value" : "Save" } }, - "hy" : { + "ne" : { "stringUnit" : { "state" : "translated", - "value" : "Ուղարկել" + "value" : "Save" } }, - "ia" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Inviar" + "value" : "Opslaan" } }, - "id" : { + "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kirim" + "value" : "Save" } }, - "is" : { + "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Senda" + "value" : "Save" } }, - "it" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Invia" + "value" : "Zapisz" } }, - "ja_JP" : { + "ps" : { "stringUnit" : { "state" : "translated", - "value" : "送る" + "value" : "Save" } }, - "ka" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Salvar" } }, - "ka_GE" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "გაგზავნა" + "value" : "Save" } }, - "kab" : { + "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Tuzna" + "value" : "Save" } }, - "km" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "ផ្ញើ" + "value" : "Save" } }, - "kn" : { + "sc" : { "stringUnit" : { "state" : "translated", - "value" : "ಕಳುಹಿಸಿ" + "value" : "Save" } }, - "ko" : { + "si" : { "stringUnit" : { "state" : "translated", - "value" : "전송" + "value" : "Save" } }, - "lb" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Schécken" + "value" : "Uložiť" } }, - "lo" : { + "sl" : { "stringUnit" : { "state" : "translated", - "value" : "ສົ່ງ" + "value" : "Save" } }, - "lt_LT" : { + "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Siųsti" + "value" : "Save" } }, - "lv" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Sūtīt" + "value" : "Save" } }, - "mk" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Прати" + "value" : "Spara" } }, - "mn" : { + "sw" : { "stringUnit" : { "state" : "translated", - "value" : "–∏–ª–≥—ç—ç—Ö" + "value" : "Hifadhi" } }, - "ms_MY" : { + "ta" : { "stringUnit" : { "state" : "translated", - "value" : "Hantar" + "value" : "Save" } }, - "nb_NO" : { + "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Save" } }, - "nl" : { + "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Verzenden" + "value" : "Save" } }, - "nn_NO" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Kaydet" } }, - "oc" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "pl" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij" + "value" : "Save" } }, - "ps" : { + "ur_PK" : { "stringUnit" : { "state" : "translated", - "value" : "لېږل" + "value" : "Save" } }, - "pt_BR" : { + "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Saqlash" } }, - "pt_PT" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Save" } }, - "ro" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Trimite" + "value" : "保存" } }, - "ru" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить" + "value" : "儲存" } }, - "si" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "යවන්න" + "value" : "儲存" } }, - "sk_SK" : { + "zu_ZA" : { "stringUnit" : { "state" : "translated", - "value" : "Odoslať" + "value" : "Save" } - }, - "sl" : { + } + } + }, + "Scan QR Code" : { + "comment" : "Button label\nNavigation bar title", + "localizations" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Pošlji" + "value" : "Scan QR-kode" } }, - "sq" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dërgo" + "value" : "QR-Code scannen" } }, - "sr" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Пошаљи" + "value" : "Scan QR Code" } }, - "sv" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Skicka" + "value" : "Scan QR Code" } }, - "sw" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tuma" + "value" : "Escanear código QR" } }, - "th_TH" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "ส่ง" + "value" : "Skaneeri QR-koodi" } }, - "tk" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Iber" + "value" : "Scanner le QR code" } }, - "tr" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Gönder" + "value" : "Scanáil Cód QR" } }, - "ug" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "ئەۋەتىڭ" + "value" : "Scansiona il QR Code" } }, - "uk" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Надіслати" + "value" : "Скенирај QR код" } }, - "ur_PK" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "بھجیں" + "value" : "Escanear código QR" } }, - "uz" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Yuborish" + "value" : "Сканировать QR код" } }, - "vi" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Gửi" + "value" : "Naskenovať QR kód" } }, - "zh_CN" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "发送" + "value" : "Kare kodu tara" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "傳送" + "value" : "掃描 QR 碼" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "傳送" + "value" : "掃描 QR code" } } } }, - "Send email" : { - "comment" : "Legacy action for notifying unified-share recipients by email; currently not displayed.", + "Send" : { + "comment" : "Primary action button title in the unified share view.", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Stuur e-pos" - } - }, - "an" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ninviar correu electronico" + "value" : "Stuur" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إرسال بريد" + "value" : "أرسِل" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Unviar un corréu electrónicu" + "value" : "Unviar" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Email yolla" + "value" : "Göndər" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Адправіць электронны ліст" + "value" : "Адправіць" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Изпращане на имейл" + "value" : "Изпращане" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "ইমেইল পাঠান " + "value" : "পাঠাও" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Kas postel" + "value" : "Kas" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji e-poštu" + "value" : "Pošalji" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correu" + "value" : "Envia" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Odeslat e-mail" + "value" : "Odeslat" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Anfon e-bost" + "value" : "Anfon" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-mail" + "value" : "Send" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail senden" + "value" : "Senden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αποστολή email" + "value" : "Αποστολή" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Send email" + "value" : "Send" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Send email" + "value" : "Send" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Sendi retpoŝtmesaĝon" + "value" : "Sendi" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Enviar" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Saada e-kiri" + "value" : "Saada" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Bidali mezu elektronikoa" + "value" : "Bidali" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "ارسال ایمیل" + "value" : "ارسال" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Lähetä sähköposti" - } - }, - "fo" : { - "stringUnit" : { - "state" : "translated", - "value" : "Send t-post" + "value" : "Lähetä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer un e-mail" + "value" : "Envoyer" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Seol ríomhphost" + "value" : "Seol" } }, - "gl" : { + "gd" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar o correo" + "value" : "Cuir" } }, - "he" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "שליחת דואר אלקטרוני" + "value" : "Enviar" } }, - "hi_IN" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "ईमेल भेजें" + "value" : "שליחה" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji poruku e-pošte" + "value" : "Pošaljite" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "E-mail küldése" + "value" : "Küldés" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Ուղարկել փոստ" + "value" : "Ուղարկել" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Inviar message de e-posta" + "value" : "Inviar" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kirim email" + "value" : "Kirim" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Senda tölvupóst" + "value" : "Senda" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia email" + "value" : "Invia" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "メールを送信" + "value" : "送る" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "ელ.ფოსტის გაგზავნა" + "value" : "Send" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "ელ-წერილის გაგზავნა" + "value" : "გაგზავნა" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Azen imayl" + "value" : "Tuzna" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "ផ្ញើ​អ៊ីមែល" + "value" : "ផ្ញើ" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "ಇ-ಅಂಚೆಯನ್ನು ಕಳುಹಿಸಿ" + "value" : "ಕಳುಹಿಸಿ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "이메일 보내기" + "value" : "전송" + } + }, + "lb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schécken" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ສົ່ງອີເມວ" + "value" : "ສົ່ງ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Siųsti elektroninį laišką" + "value" : "Siųsti" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Nosūtīt e-pasta ziņojumu" + "value" : "Sūtīt" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Испрати е-пошта" + "value" : "Прати" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Мэйл илгээх" + "value" : "–∏–ª–≥—ç—ç—Ö" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Menghantar E-mel" + "value" : "Hantar" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-post" + "value" : "Send" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mail verzenden" + "value" : "Verzenden" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-post" + "value" : "Send" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar email" + "value" : "Enviar" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij e-mail" + "value" : "Wyślij" + } + }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "لېږل" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar e-mail" + "value" : "Enviar" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar mensagem" + "value" : "Enviar" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Trimite email" + "value" : "Trimite" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить письмо" + "value" : "Отправить" } }, - "sc" : { + "si" : { "stringUnit" : { "state" : "translated", - "value" : "Imbia messàgiu de posta eletrònica" + "value" : "යවන්න" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odoslať email" + "value" : "Odoslať" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Pošlji elektronsko sporočilo" + "value" : "Pošlji" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Dërgo email" + "value" : "Dërgo" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Пошаљи е-пошту" + "value" : "Пошаљи" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Skicka e-post" + "value" : "Skicka" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Tuma barua pepe" + "value" : "Tuma" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "ส่งอีเมล" - } - }, - "tk" : { - "stringUnit" : { - "state" : "translated", - "value" : "E-poçta iber" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "E-posta gönder" - } - }, - "ug" : { - "stringUnit" : { - "state" : "translated", - "value" : "ئېلېكترونلۇق خەت ئەۋەتىڭ" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Запрошення" - } - }, - "uz" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xabar yuborish" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gửi email" - } - }, - "zh_CN" : { - "stringUnit" : { - "state" : "translated", - "value" : "发送电子邮件" - } - }, - "zh_HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "傳送電子郵件" - } - }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "傳送電子郵件" - } - } - } - }, - "Server Address" : { - "comment" : "Label for text field.", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serveradresse" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serveradresse" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server Address" - } - }, - "en_GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server Address" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dirección del servidor" - } - }, - "et_EE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serveri aadress" + "value" : "ส่ง" } }, - "fr" : { + "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse du serveur" + "value" : "Iber" } }, - "ga" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Seoladh an Fhreastalaí" + "value" : "Gönder" } }, - "it" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo del server" + "value" : "ئەۋەتىڭ" } }, - "mk" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Адреса на сервер" + "value" : "Надіслати" } }, - "pt_BR" : { + "ur_PK" : { "stringUnit" : { "state" : "translated", - "value" : "Endereço do servidor" + "value" : "بھجیں" } }, - "ru" : { + "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес сервера" + "value" : "Yuborish" } }, - "sk_SK" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Adresa servera" + "value" : "Gửi" } }, - "tr" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Sunucu adresi" + "value" : "发送" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "伺服器位址" + "value" : "傳送" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "伺服器位址" + "value" : "傳送" } } } }, - "Settings" : { - "comment" : "Settings section title in the unified share view.", + "Send email" : { + "comment" : "Legacy action for notifying unified-share recipients by email; currently not displayed.", + "extractionState" : "stale", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Instellings" + "value" : "Stuur e-pos" } }, "an" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Ninviar correu electronico" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الإعدادات" + "value" : "إرسال بريد" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Unviar un corréu electrónicu" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Quraşdırmalar" + "value" : "Email yolla" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Налады" + "value" : "Адправіць электронны ліст" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Изпращане на имейл" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "সেটিংস" + "value" : "ইমেইল পাঠান " } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Arventennoù" + "value" : "Kas postel" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Podešavanje" + "value" : "Pošalji e-poštu" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Paràmetres" + "value" : "Enviar correu" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Odeslat e-mail" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Gosodiadau" + "value" : "Anfon e-bost" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indstillinger" + "value" : "Send e-mail" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" + "value" : "E-Mail senden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ρυθμίσεις" + "value" : "Αποστολή email" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Send email" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Send email" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Agordoj" + "value" : "Sendi retpoŝtmesaĝon" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Enviar correo electrónico" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Enviar correo electrónico" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Enviar correo electrónico" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Enviar correo electrónico" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Enviar correo electrónico" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Enviar correo electrónico" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Enviar correo electrónico" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Seadistused" + "value" : "Saada e-kiri" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Ezarpenak" + "value" : "Bidali mezu elektronikoa" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "تنظیمات" + "value" : "ارسال ایمیل" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Asetukset" + "value" : "Lähetä sähköposti" } }, - "fr" : { + "fo" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Send t-post" } }, - "ga" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Socruithe" + "value" : "Envoyer un e-mail" } }, - "gd" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Roghainnean" + "value" : "Seol ríomhphost" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Axustes" + "value" : "Enviar o correo" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הגדרות" + "value" : "שליחת דואר אלקטרוני" + } + }, + "hi_IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ईमेल भेजें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke" + "value" : "Pošalji poruku e-pošte" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Beállítások" + "value" : "E-mail küldése" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Կարգավորումներ" + "value" : "Ուղարկել փոստ" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Configurationes" + "value" : "Inviar message de e-posta" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengaturan" + "value" : "Kirim email" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Stillingar" + "value" : "Senda tölvupóst" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni" + "value" : "Invia email" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "メールを送信" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "ოფციები" + "value" : "ელ.ფოსტის გაგზავნა" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "პარამეტრები" + "value" : "ელ-წერილის გაგზავნა" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Iɣewwaren" + "value" : "Azen imayl" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "ការកំណត់" + "value" : "ផ្ញើ​អ៊ីមែល" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "ಆಯ್ಕೆ" + "value" : "ಇ-ಅಂಚೆಯನ್ನು ಕಳುಹಿಸಿ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "설정" - } - }, - "lb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Astellungen" + "value" : "이메일 보내기" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ການຕັ້ງຄ່າ" + "value" : "ສົ່ງອີເມວ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Nustatymai" + "value" : "Siųsti elektroninį laišką" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Iestatījumi" + "value" : "Nosūtīt e-pasta ziņojumu" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Параметри" + "value" : "Испрати е-пошта" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "–¢–æ—Ö–∏—Ä–≥–æ–æ" - } - }, - "mr" : { - "stringUnit" : { - "state" : "translated", - "value" : "सेटिंग" + "value" : "Мэйл илгээх" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapan" + "value" : "Menghantar E-mel" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillinger" - } - }, - "ne" : { - "stringUnit" : { - "state" : "translated", - "value" : "सेटिङ्हरू" + "value" : "Send e-post" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen" + "value" : "E-mail verzenden" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillingar" + "value" : "Send e-post" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Enviar email" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia" - } - }, - "ps" : { - "stringUnit" : { - "state" : "translated", - "value" : "سمونې" + "value" : "Wyślij e-mail" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurações" + "value" : "Enviar e-mail" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Definições" + "value" : "Enviar mensagem" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Setări" + "value" : "Trimite email" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Отправить письмо" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Cunfiguratzione" - } - }, - "si" : { - "stringUnit" : { - "state" : "translated", - "value" : "සැකසුම්" + "value" : "Imbia messàgiu de posta eletrònica" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavenia" + "value" : "Odoslať email" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavitve" + "value" : "Pošlji elektronsko sporočilo" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Rregullimet" + "value" : "Dërgo email" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Поставке" + "value" : "Пошаљи е-пошту" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Inställningar" + "value" : "Skicka e-post" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Mipangilio" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "அமைப்புகள்" + "value" : "Tuma barua pepe" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่า" + "value" : "ส่งอีเมล" } }, "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Sazlamalar" + "value" : "E-poçta iber" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Ayarlar" + "value" : "E-posta gönder" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "تەڭشەكلەر" + "value" : "ئېلېكترونلۇق خەت ئەۋەتىڭ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Налаштування" - } - }, - "ur_PK" : { - "stringUnit" : { - "state" : "translated", - "value" : "سیٹینگز" + "value" : "Запрошення" } }, "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Sozlamalar" + "value" : "Xabar yuborish" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Thiết lập" + "value" : "Gửi email" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "设置" + "value" : "发送电子邮件" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "傳送電子郵件" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "傳送電子郵件" } } } }, - "Remove password" : { - "comment" : "Destructive action that removes the password from a share.", + "Server Address" : { + "comment" : "Label for text field.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Fjern adgangskode" + "value" : "Serveradresse" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Passwort entfernen" + "value" : "Serveradresse" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Remove password" + "value" : "Server Address" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Remove password" + "value" : "Server Address" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar contraseña" + "value" : "Dirección del servidor" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Eemalda salaõna" + "value" : "Serveri aadress" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer le mot de passe" + "value" : "Adresse du serveur" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Bain an focal faire" + "value" : "Seoladh an Fhreastalaí" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rimuovi la password" + "value" : "Indirizzo del server" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Адреса на сервер" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Remover senha" + "value" : "Endereço do servidor" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить пароль" + "value" : "Адрес сервера" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odstrániť heslo" + "value" : "Adresa servera" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Parolayı kaldır" + "value" : "Sunucu adresi" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "移除密碼" + "value" : "伺服器位址" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "移除密碼" + "value" : "伺服器位址" } } } }, - "Save" : { - "comment" : "Button that confirms a text property change.", + "Settings" : { + "comment" : "Settings section title in the unified share view.", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Instellings" } }, "an" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuración" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "الإعدادات" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuración" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Quraşdırmalar" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Налады" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Настройки" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "সেটিংস" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Arventennoù" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Podešavanje" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Desa" + "value" : "Paràmetres" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Uložit" + "value" : "Settings" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Gosodiadau" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Gem" + "value" : "Indstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Speichern" + "value" : "Einstellungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αποθήκευση" + "value" : "Ρυθμίσεις" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Settings" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Settings" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Agordoj" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Ajustes" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ajustes" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuración" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ajustes" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ajustes" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ajustes" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configuraciones " } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Salvesta" + "value" : "Seadistused" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ezarpenak" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "ذخیره" + "value" : "تنظیمات" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Save" - } - }, - "fo" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "Asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer" + "value" : "Paramètres" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Sábháil" + "value" : "Socruithe" } }, "gd" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Roghainnean" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Gardar" + "value" : "Axustes" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "Save" - } - }, - "hi_IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "הגדרות" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Spremi" - } - }, - "hsb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "Postavke" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Mentés" + "value" : "Beállítások" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Կարգավորումներ" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Configurationes" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Simpan" - } - }, - "ig" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "Pengaturan" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Vista" + "value" : "Stillingar" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Impostazioni" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "保存する" + "value" : "設定" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ოფციები" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "პარამეტრები" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Sekles" + "value" : "Iɣewwaren" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ការកំណត់" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ಆಯ್ಕೆ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Save" - } - }, - "la" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "설정" } }, "lb" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Astellungen" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ການຕັ້ງຄ່າ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Išsaugoti" + "value" : "Nustatymai" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Iestatījumi" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Параметри" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "–•–∞–¥–≥–∞–ª–∞—Ö" + "value" : "–¢–æ—Ö–∏—Ä–≥–æ–æ" } }, "mr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "सेटिंग" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" - } - }, - "my" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "Tetapan" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Innstillinger" } }, "ne" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "सेटिङ्हरू" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opslaan" + "value" : "Instellingen" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Innstillingar" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Paramètres" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz" + "value" : "Ustawienia" } }, "ps" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "سمونې" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Salvar" + "value" : "Configurações" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Definições" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Setări" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Настройки" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Cunfiguratzione" } }, "si" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "සැකසුම්" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Uložiť" + "value" : "Nastavenia" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Nastavitve" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Rregullimet" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Поставке" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Spara" + "value" : "Inställningar" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Hifadhi" + "value" : "Mipangilio" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "அமைப்புகள்" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "การตั้งค่า" } }, "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Sazlamalar" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kaydet" + "value" : "Ayarlar" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "تەڭشەكلەر" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Налаштування" } }, "ur_PK" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "سیٹینگز" } }, "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Saqlash" + "value" : "Sozlamalar" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Thiết lập" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "保存" + "value" : "设置" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "儲存" + "value" : "設定" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "儲存" - } - }, - "zu_ZA" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" + "value" : "設定" } } } @@ -11364,6 +11369,7 @@ }, "Share link" : { "comment" : "Row title for a public link share.", + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -11789,6 +11795,7 @@ }, "Share link (%lld)" : { "comment" : "Row title for the nth public link share of a file.", + "extractionState" : "stale", "localizations" : { "da" : { "stringUnit" : { @@ -11977,572 +11984,572 @@ } } }, - "This change will replace the individual permission settings of all recipients in this share." : { - "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", + "The address of your Nextcloud web interface when you open it in your browser." : { + "comment" : "Label below the server address field in the login view.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Denne ændring erstatter de individuelle rettigheder for alle modtagere af denne deling." + "value" : "Adressen på din Nextcloud-webgrænseflade, når du åbner den i din browser." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Änderung ersetzt die individuellen Berechtigungseinstellungen aller Empfänger dieser Freigabe." + "value" : "Die Adresse Ihrer Nextcloud-Weboberfläche, wenn Sie diese in Ihrem Browser öffnen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This change will replace the individual permission settings of all recipients in this share." + "value" : "The address of your Nextcloud web interface when you open it in your browser." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This change will replace the individual permission settings of all recipients in this share." + "value" : "The address of your Nextcloud web interface when you open it in your browser." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El cambio reemplazará los ajustes de permisos individuales de todos los destinatarios de este recurso compartido." + "value" : "La dirección de la interfaz web Nextcloud cuando la abras en tu navegador." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Selle muudatusega asendad kõikide jagamise saajate isiklikud õigused üldiste õigustega." + "value" : "Sinu Nextcloudi kasutajaliidese veebiaadress, kui sa avad ta veebibrauseris." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cette modification remplacera les paramètres des autorisations individuelles de tous les destinataires de ce partage." + "value" : "L'adresse de votre interface web Nextcloud quand vous l'ouvrez dans votre navigateur." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cuirfidh an t-athrú seo ionad na socruithe ceada aonair do gach faighteoir sa scair seo." + "value" : "Seoladh do chomhéadain gréasáin Nextcloud nuair a osclaíonn tú é i do bhrabhsálaí." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questa modifica sostituirà le impostazioni di autorizzazione individuali di tutti i destinatari di questa condivisione." + "value" : "L'indirizzo dell'interfaccia web di Nextcloud quando la apri nel browser." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Essa alteração substituirá as configurações de permissão individuais de todos os destinatários deste compartilhamento." + "value" : "O endereço do site Nextcloud ao abri-lo no navegador." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Это изменение заменит индивидуальные настройки прав доступа всех получателей в этом общем ресурсе." + "value" : "Адрес Вашего веб-интерфейса Nextcloud, когда вы открываете его в Вашем браузере." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Táto zmena nahradí individuálne nastavenia oprávnení všetkých príjemcov v tomto zdieľaní." + "value" : "Adresa vášho webového rozhrania Nextcloud, keď ho otvoríte v prehliadači." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu değişiklik, bu paylaşımdaki tüm alıcıların bireysel izin ayarlarının yerini alacak." + "value" : "Nextcloud internet arayüzü için tarayıcınızda açacağınız adres." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此變更會取代此分享中所有接收者的個別權限設定。" + "value" : "你在瀏覽器中開啟 Nextcloud 網頁介面時使用的位址。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此變更會取代此分享中所有收件人的個別權限設定。" + "value" : "您在瀏覽器中開啟 Nextcloud 網頁介面的位址。" } } } }, - "The address of your Nextcloud web interface when you open it in your browser." : { - "comment" : "Label below the server address field in the login view.", + "The entered server address is invalid." : { + "comment" : "This is an error message.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Adressen på din Nextcloud-webgrænseflade, når du åbner den i din browser." + "value" : "Den indtastede serveradresse er ugyldig." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die Adresse Ihrer Nextcloud-Weboberfläche, wenn Sie diese in Ihrem Browser öffnen." + "value" : "Die eingegebene Serveradresse ist ungültig." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The address of your Nextcloud web interface when you open it in your browser." + "value" : "The entered server address is invalid." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The address of your Nextcloud web interface when you open it in your browser." + "value" : "The entered server address is invalid." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La dirección de la interfaz web Nextcloud cuando la abras en tu navegador." + "value" : "La dirección del servidor ingresada no es válida." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sinu Nextcloudi kasutajaliidese veebiaadress, kui sa avad ta veebibrauseris." + "value" : "Sisestatud serveriaadress on vigane." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'adresse de votre interface web Nextcloud quand vous l'ouvrez dans votre navigateur." + "value" : "L'adresse du serveur saisie n'est pas valide." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Seoladh do chomhéadain gréasáin Nextcloud nuair a osclaíonn tú é i do bhrabhsálaí." + "value" : "Tá an seoladh freastalaí a iontráladh neamhbhailí." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'indirizzo dell'interfaccia web di Nextcloud quando la apri nel browser." + "value" : "L'indirizzo del server non è valido." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O endereço do site Nextcloud ao abri-lo no navegador." + "value" : "O endereço do servidor inserido é inválido." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес Вашего веб-интерфейса Nextcloud, когда вы открываете его в Вашем браузере." + "value" : "Введённый адрес сервера недействителен." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Adresa vášho webového rozhrania Nextcloud, keď ho otvoríte v prehliadači." + "value" : "Zadaná adresa servera je neplatná." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Nextcloud internet arayüzü için tarayıcınızda açacağınız adres." + "value" : "Yazılan sunucu adresi geçersiz." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "你在瀏覽器中開啟 Nextcloud 網頁介面時使用的位址。" + "value" : "輸入的伺服器位址無效。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "您在瀏覽器中開啟 Nextcloud 網頁介面的位址。" + "value" : "輸入的伺服器位址無效。" } } } }, - "The entered server address is invalid." : { - "comment" : "This is an error message.", + "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." : { + "comment" : "Help text below the editable token of a custom public link.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Den indtastede serveradresse er ugyldig." + "value" : "Linket kan ændres, så det er let at huske, men vælg ikke noget, der er let at gætte." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die eingegebene Serveradresse ist ungültig." + "value" : "Der Link kann so geändert werden, dass er leicht zu merken ist, aber wählen Sie keinen Link, der leicht zu erraten ist." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The entered server address is invalid." + "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The entered server address is invalid." + "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La dirección del servidor ingresada no es válida." + "value" : "El enlace puede ser cambiado para ser fácil de recordar, pero no lo cambies a algo que sea fácil de adivinar." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sisestatud serveriaadress on vigane." + "value" : "Seda linki saad muuta kergesti meeldejäetavaks, aga palun ära muuda selliseks, mis oleks liiga lihtne." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'adresse du serveur saisie n'est pas valide." + "value" : "Le lien peut être changé pour être plus simple à retenir, mais ne le définissez pas sur quelque chose de facile à deviner." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Tá an seoladh freastalaí a iontráladh neamhbhailí." + "value" : "Is féidir an nasc a athrú ionas go mbeidh sé éasca a mheabhrú, ach ná socraigh é ar rud éigin atá éasca a thomhas." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'indirizzo del server non è valido." + "value" : "Il link può essere modificato per renderlo facile da ricordare, ma non impostarlo su qualcosa di facile da indovinare." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O endereço do servidor inserido é inválido." + "value" : "O link pode ser alterado para ficar fácil de lembrar, mas não o defina como algo que seja fácil de adivinhar." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введённый адрес сервера недействителен." + "value" : "Ссылку можно изменить так, чтобы её было легко запомнить, но не стоит выбирать название, которое легко угадать." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Zadaná adresa servera je neplatná." + "value" : "Odkaz je možné zmeniť tak, aby sa dal ľahko zapamätať, ale nenastavujte ho na niečo, čo sa dá ľahko uhádnuť." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yazılan sunucu adresi geçersiz." + "value" : "Bağlantı hatırlanması kolay olacak şekilde değiştirilebilir. Ancak kolay bulunabilecek bir şeye ayarlamayın." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "輸入的伺服器位址無效。" + "value" : "你可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "輸入的伺服器位址無效。" + "value" : "您可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" } } } }, - "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." : { - "comment" : "Help text below the editable token of a custom public link.", + "This change will replace the individual permission settings of all recipients in this share." : { + "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Linket kan ændres, så det er let at huske, men vælg ikke noget, der er let at gætte." + "value" : "Denne ændring erstatter de individuelle rettigheder for alle modtagere af denne deling." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der Link kann so geändert werden, dass er leicht zu merken ist, aber wählen Sie keinen Link, der leicht zu erraten ist." + "value" : "Diese Änderung ersetzt die individuellen Berechtigungseinstellungen aller Empfänger dieser Freigabe." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." + "value" : "This change will replace the individual permission settings of all recipients in this share." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." + "value" : "This change will replace the individual permission settings of all recipients in this share." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El enlace puede ser cambiado para ser fácil de recordar, pero no lo cambies a algo que sea fácil de adivinar." + "value" : "El cambio reemplazará los ajustes de permisos individuales de todos los destinatarios de este recurso compartido." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Seda linki saad muuta kergesti meeldejäetavaks, aga palun ära muuda selliseks, mis oleks liiga lihtne." + "value" : "Selle muudatusega asendad kõikide jagamise saajate isiklikud õigused üldiste õigustega." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le lien peut être changé pour être plus simple à retenir, mais ne le définissez pas sur quelque chose de facile à deviner." + "value" : "Cette modification remplacera les paramètres des autorisations individuelles de tous les destinataires de ce partage." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Is féidir an nasc a athrú ionas go mbeidh sé éasca a mheabhrú, ach ná socraigh é ar rud éigin atá éasca a thomhas." + "value" : "Cuirfidh an t-athrú seo ionad na socruithe ceada aonair do gach faighteoir sa scair seo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il link può essere modificato per renderlo facile da ricordare, ma non impostarlo su qualcosa di facile da indovinare." + "value" : "Questa modifica sostituirà le impostazioni di autorizzazione individuali di tutti i destinatari di questa condivisione." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O link pode ser alterado para ficar fácil de lembrar, mas não o defina como algo que seja fácil de adivinhar." + "value" : "Essa alteração substituirá as configurações de permissão individuais de todos os destinatários deste compartilhamento." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ссылку можно изменить так, чтобы её было легко запомнить, но не стоит выбирать название, которое легко угадать." + "value" : "Это изменение заменит индивидуальные настройки прав доступа всех получателей в этом общем ресурсе." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odkaz je možné zmeniť tak, aby sa dal ľahko zapamätať, ale nenastavujte ho na niečo, čo sa dá ľahko uhádnuť." + "value" : "Táto zmena nahradí individuálne nastavenia oprávnení všetkých príjemcov v tomto zdieľaní." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bağlantı hatırlanması kolay olacak şekilde değiştirilebilir. Ancak kolay bulunabilecek bir şeye ayarlamayın." + "value" : "Bu değişiklik, bu paylaşımdaki tüm alıcıların bireysel izin ayarlarının yerini alacak." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "你可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" + "value" : "此變更會取代此分享中所有接收者的個別權限設定。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "您可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" + "value" : "此變更會取代此分享中所有收件人的個別權限設定。" } } } }, - "This share is limited to \"%@\". You can only grant the same or fewer permissions." : { - "comment" : "Explains that recipient permissions cannot exceed the share-wide preset; the placeholder is the preset name.", + "This recipient will be removed from the share." : { + "comment" : "Message of the recipient removal confirmation dialog.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Denne deling er begrænset til \"%1$@\". Du kan kun give de samme eller færre rettigheder." + "value" : "Modtageren fjernes fra delingen." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Freigabe ist auf \"%1$@\" beschränkt. Sie können nur dieselben oder weniger Berechtigungen erteilen." + "value" : "Dieser Empfänger wird aus der Freigabe entfernt." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." + "value" : "This recipient will be removed from the share." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." + "value" : "This recipient will be removed from the share." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Este recurso compartido está limitado a %1$@\". Sólo puedes dar los mismos o menos permisos." + "value" : "Este destinatario será eliminado del recurso compartido." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sellel jagamisel kehtib piirang (%1$@) ja sa võid jagada vaid samade või vähemate õigustega." + "value" : "See saaja eemaldatakse jagamisest." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ce partage est limité à \"%1$@\". Vous pouvez accorder, au maximum, ce nombre d'autorisations." + "value" : "Ce destinataire sera retiré du partage." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Tá an comhroinnt seo teoranta do \"%1$@\". Ní féidir leat ach na ceadanna céanna nó níos lú ná iad a dheonú." + "value" : "Bainfear an faighteoir seo den scair." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questa quota è limitata a \"%1$@\". È possibile concedere solo gli stessi permessi o un numero inferiore di permessi." + "value" : "Questo destinatario verrà rimosso dalla condivisione." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Essa compartilhamento está limitado a “%1$@”. Você só pode conceder as mesmas permissões ou menos do que essas." + "value" : "Esse destinatário será removido do compartilhamento." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Этот общий ресурс ограничен правами \"%1$@\". Вы можете предоставить только те же или меньшие права доступа." + "value" : "Этот получатель будет удален из общего доступа." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Toto zdieľanie je obmedzené na \"%1$@\". Môžete udeliť len rovnaké alebo menšie oprávnenia." + "value" : "Tento príjemca bude odstránený zo zdieľania." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu paylaşım \"%1$@\" ile sınırlı. Yalnızca aynı veya daha az izinler verebilirsiniz." + "value" : "Bu alıcı paylaşımdan kaldırılacak." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此分享的權限僅限於「%1$@」。你只能授予相同或更少的權限。" + "value" : "此接收者將會從分享中移除。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此分享的權限僅限於「%1$@」。您僅能授予相同或更少的權限。" + "value" : "此收件人將會從分享中移除。" } } } }, - "This recipient will be removed from the share." : { - "comment" : "Message of the recipient removal confirmation dialog.", + "This share is limited to \"%@\". You can only grant the same or fewer permissions." : { + "comment" : "Explains that recipient permissions cannot exceed the share-wide preset; the placeholder is the preset name.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modtageren fjernes fra delingen." + "value" : "Denne deling er begrænset til \"%1$@\". Du kan kun give de samme eller færre rettigheder." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dieser Empfänger wird aus der Freigabe entfernt." + "value" : "Diese Freigabe ist auf \"%1$@\" beschränkt. Sie können nur dieselben oder weniger Berechtigungen erteilen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This recipient will be removed from the share." + "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This recipient will be removed from the share." + "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Este destinatario será eliminado del recurso compartido." + "value" : "Este recurso compartido está limitado a %1$@\". Sólo puedes dar los mismos o menos permisos." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "See saaja eemaldatakse jagamisest." + "value" : "Sellel jagamisel kehtib piirang (%1$@) ja sa võid jagada vaid samade või vähemate õigustega." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ce destinataire sera retiré du partage." + "value" : "Ce partage est limité à \"%1$@\". Vous pouvez accorder, au maximum, ce nombre d'autorisations." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Bainfear an faighteoir seo den scair." + "value" : "Tá an comhroinnt seo teoranta do \"%1$@\". Ní féidir leat ach na ceadanna céanna nó níos lú ná iad a dheonú." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questo destinatario verrà rimosso dalla condivisione." + "value" : "Questa quota è limitata a \"%1$@\". È possibile concedere solo gli stessi permessi o un numero inferiore di permessi." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Esse destinatário será removido do compartilhamento." + "value" : "Essa compartilhamento está limitado a “%1$@”. Você só pode conceder as mesmas permissões ou menos do que essas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Этот получатель будет удален из общего доступа." + "value" : "Этот общий ресурс ограничен правами \"%1$@\". Вы можете предоставить только те же или меньшие права доступа." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Tento príjemca bude odstránený zo zdieľania." + "value" : "Toto zdieľanie je obmedzené na \"%1$@\". Môžete udeliť len rovnaké alebo menšie oprávnenia." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu alıcı paylaşımdan kaldırılacak." + "value" : "Bu paylaşım \"%1$@\" ile sınırlı. Yalnızca aynı veya daha az izinler verebilirsiniz." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此接收者將會從分享中移除。" + "value" : "此分享的權限僅限於「%1$@」。你只能授予相同或更少的權限。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此收件人將會從分享中移除。" + "value" : "此分享的權限僅限於「%1$@」。您僅能授予相同或更少的權限。" } } } @@ -12644,4 +12651,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift index a311be03..c784d72e 100644 --- a/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift +++ b/Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift @@ -158,7 +158,7 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing { private func message(for error: NKError) -> String { let isInstanceNotFound = error.kind == .notFound || error.kind == .invalidResponse - if isInstanceNotFound, error.userFacingServerMessage == nil { + if isInstanceNotFound { return String(localized: "No server was found at this address.", bundle: .module, comment: "Login error message when the entered address does not point to a Nextcloud server.") } diff --git a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift index 50ca8c68..d2794da1 100644 --- a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift @@ -8,8 +8,8 @@ import SwiftyJSON import Testing @testable import NextcloudKit -/// Verifies that `NKError` classifies failures into a `Kind` and keeps the raw server details apart. -@Suite("NKError kind and server details") +/// Verifies that `NKError` classifies failures into a `Kind` and only passes on the share API's messages. +@Suite("NKError kind and server message") struct NKErrorKindTests { static let urlErrorCases: [(Int, NKError.Kind)] = [ (NSURLErrorNotConnectedToInternet, .offline), @@ -39,7 +39,15 @@ struct NKErrorKindTests { (507, .quotaExceeded) ] - private let davURL = URL(string: "https://cloud.example.com/remote.php/dav/files/alice/Documents")! + static let ocsStatusCases: [(Int, NKError.Kind?)] = [ + (200, nil), + (403, .forbidden), + (500, .serverError), + (997, .unauthorized) + ] + + private static let davURL = URL(string: "https://cloud.example.com/remote.php/dav/files/alice/Documents")! + private static let shareURL = URL(string: "https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares/42")! private func davErrorBody(exception: String, message: String) -> Data { Data(""" @@ -51,11 +59,15 @@ struct NKErrorKindTests { """.utf8) } - private func responseError(statusCode: Int, method: String, body: Data?) -> NKError { - var request = URLRequest(url: davURL) + private func ocsErrorBody(statusCode: Int, message: String) -> Data { + Data(#"{"ocs":{"meta":{"status":"failure","statuscode":\#(statusCode),"message":"\#(message)"},"data":[]}}"#.utf8) + } + + private func responseError(url: URL = NKErrorKindTests.davURL, statusCode: Int, method: String, body: Data?) -> NKError { + var request = URLRequest(url: url) request.httpMethod = method - let response = HTTPURLResponse(url: davURL, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil) + let response = HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil) let dataResponse = AFDataResponse(request: request, response: response, data: body, metrics: nil, serializationDuration: 0, result: .failure(.responseValidationFailed(reason: .unacceptableStatusCode(code: statusCode)))) return NKError(error: dataResponse.error, afResponse: dataResponse, responseData: body) @@ -63,10 +75,7 @@ struct NKErrorKindTests { @Test("Classifies URL loading errors", arguments: NKErrorKindTests.urlErrorCases) func urlErrors(code: Int, kind: NKError.Kind) { - let error = NKError(nsError: NSError(domain: NSURLErrorDomain, code: code)) - - #expect(error.kind == kind) - #expect(error.httpStatusCode == nil) + #expect(NKError(nsError: NSError(domain: NSURLErrorDomain, code: code)).kind == kind) } @Test("Classifies HTTP statuses without changing the description", arguments: NKErrorKindTests.httpStatusCases) @@ -74,10 +83,16 @@ struct NKErrorKindTests { let error = NKError(statusCode: statusCode, fallbackDescription: "Fallback") #expect(error.kind == kind) - #expect(error.httpStatusCode == statusCode) #expect(error.errorDescription.hasPrefix("\(statusCode): ")) } + @Test("Classifies OCS status codes, including OCS v1 ones", arguments: NKErrorKindTests.ocsStatusCases) + func ocsStatusCodes(statusCode: Int, kind: NKError.Kind?) throws { + let json = try JSON(data: ocsErrorBody(statusCode: statusCode, message: "Message")) + + #expect(NKError(rootJson: json, fallbackStatusCode: 200).kind == kind) + } + @Test("Uses the request method to tell WebDAV statuses apart") func requestMethod() { let body = davErrorBody(exception: "Sabre\\DAV\\Exception\\MethodNotAllowed", message: "The resource you tried to create already exists") @@ -89,86 +104,42 @@ struct NKErrorKindTests { #expect(responseError(statusCode: 412, method: "PUT", body: body).kind == .conflict) } - @Test("Reads the WebDAV exception and message, which stay out of the UI") - func davDetails() { + @Test("Keeps WebDAV messages in the logs only") + func davMessage() { let error = responseError(statusCode: 409, method: "MKCOL", body: davErrorBody(exception: "Sabre\\DAV\\Exception\\Conflict", message: "Parent node does not exist")) #expect(error.kind == .conflict) - #expect(error.httpStatusCode == 409) - #expect(error.ocsStatusCode == nil) - #expect(error.davException == "Sabre\\DAV\\Exception\\Conflict") - #expect(error.serverMessage == "Parent node does not exist") - #expect(error.userFacingServerMessage == nil) + #expect(error.userFriendlyServerMessage == nil) #expect(error.errorDescription == "Parent node does not exist") } - @Test("Treats WebDAV file name rule violations as user-facing") - func invalidPathIsUserFacing() { - let message = "\"/\" is not allowed inside a file or folder name." - let error = responseError(statusCode: 400, method: "MKCOL", body: davErrorBody(exception: "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath", message: message)) + @Test("Recognizes a rejected file or folder name") + func invalidPath() { + let body = davErrorBody(exception: "OCA\\DAV\\Connector\\Sabre\\Exception\\InvalidPath", message: "\"/\" is not allowed inside a file or folder name.") + let error = responseError(statusCode: 400, method: "MKCOL", body: body) - #expect(error.kind == .unknown) - #expect(error.userFacingServerMessage == message) + #expect(error.kind == .invalidName) + #expect(error.userFriendlyServerMessage == nil) } - @Test("Reads the OCS status code and message") - func ocsDetails() throws { - let json = try JSON(data: Data(#"{"ocs":{"meta":{"status":"failure","statuscode":403,"message":"Group sharing is disabled by the administrator"},"data":[]}}"#.utf8)) - let error = NKError(rootJson: json, fallbackStatusCode: 403) + @Test("Passes on the share API's messages") + func shareAPIMessage() { + let error = responseError(url: Self.shareURL, statusCode: 403, method: "PUT", body: ocsErrorBody(statusCode: 403, message: "Public upload disabled by the administrator")) #expect(error.kind == .forbidden) - #expect(error.httpStatusCode == 403) - #expect(error.ocsStatusCode == 403) - #expect(error.serverMessage == "Group sharing is disabled by the administrator") - #expect(error.userFacingServerMessage == "Group sharing is disabled by the administrator") - #expect(error.errorDescription == "Group sharing is disabled by the administrator") - } - - @Test("Takes the message from a string `ocs.data`, as the unified sharing API sends it") - func ocsDataMessage() { - let body = Data(#"{"ocs":{"meta":{"statuscode":400,"message":""},"data":"Password needs to be at least 10 characters long"}}"#.utf8) - let error = responseError(statusCode: 400, method: "POST", body: body) - - #expect(error.ocsStatusCode == 400) - #expect(error.userFacingServerMessage == "Password needs to be at least 10 characters long") - } - - @Test("Keeps OCS server errors out of the UI") - func ocsServerError() throws { - let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":500,"message":"Internal error"},"data":[]}}"#.utf8)) - let error = NKError(rootJson: json, fallbackStatusCode: 500) - - #expect(error.kind == .serverError) - #expect(error.serverMessage == "Internal error") - #expect(error.userFacingServerMessage == nil) - } - - @Test("Maps OCS v1 status codes") - func ocsV1StatusCodes() throws { - let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":997,"message":"Current user is not logged in"},"data":[]}}"#.utf8)) - let error = NKError(rootJson: json, fallbackStatusCode: 200) - - #expect(error.kind == .unauthorized) - #expect(error.userFacingServerMessage == nil) - } - - @Test("A successful OCS response has no kind") - func ocsSuccess() throws { - let json = try JSON(data: Data(#"{"ocs":{"meta":{"statuscode":200,"message":"OK"},"data":[]}}"#.utf8)) - let error = NKError(rootJson: json, fallbackStatusCode: 200) - - #expect(error.errorCode == 0) - #expect(error.kind == nil) + #expect(error.userFriendlyServerMessage == "Public upload disabled by the administrator") } - @Test("Reads server details from the response data of explicit errors") - func explicitErrorWithResponseData() { - let body = Data(#"{"ocs":{"meta":{"statuscode":404},"data":"Share not found"}}"#.utf8) - let error = NKError(errorCode: 404, errorDescription: "Share not found", responseData: body) + @Test("Keeps the messages of other OCS APIs in the logs only", arguments: [ + "https://cloud.example.com/ocs/v2.php/apps/sharing/api/v1/share/abc", + "https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/sharees" + ]) + func otherOCSMessages(url: String) throws { + let error = responseError(url: try #require(URL(string: url)), statusCode: 404, method: "GET", body: ocsErrorBody(statusCode: 404, message: "Share not found")) #expect(error.kind == .notFound) - #expect(error.ocsStatusCode == 404) - #expect(error.userFacingServerMessage == "Share not found") + #expect(error.userFriendlyServerMessage == nil) + #expect(error.errorDescription == "Share not found") } @Test("Derives the kind of explicit and preset errors") @@ -198,7 +169,7 @@ struct NKErrorKindTests { @Test("Classifies transport failures without a response") func transportFailure() { - let dataResponse = AFDataResponse(request: URLRequest(url: davURL), response: nil, data: nil, metrics: nil, serializationDuration: 0, result: .failure(.sessionTaskFailed(error: URLError(.notConnectedToInternet)))) + let dataResponse = AFDataResponse(request: URLRequest(url: Self.davURL), response: nil, data: nil, metrics: nil, serializationDuration: 0, result: .failure(.sessionTaskFailed(error: URLError(.notConnectedToInternet)))) let error = NKError(error: dataResponse.error, afResponse: dataResponse) #expect(error.kind == .offline) diff --git a/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift index f1b065d3..18648ddc 100644 --- a/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2026 Milen Pivchev // SPDX-License-Identifier: GPL-3.0-or-later +import Alamofire import Foundation import Testing @testable import NextcloudKit @@ -24,18 +25,12 @@ struct NKErrorLocalizedGenericMessageTests { #expect(messages.count == NKError.Kind.allCases.count) } - @Test func prefersUserFacingServerMessage() { - let body = Data(#"{"ocs":{"meta":{"statuscode":400},"data":"Password needs to be at least 10 characters long"}}"#.utf8) - let error = NKError(errorCode: 400, errorDescription: "400: Bad request", responseData: body) + @Test func prefersUserFriendlyServerMessage() throws { + let url = try #require(URL(string: "https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares")) + let body = Data(#"{"ocs":{"meta":{"statuscode":400,"message":"Password needs to be at least 10 characters long"},"data":[]}}"#.utf8) + let response = HTTPURLResponse(url: url, statusCode: 400, httpVersion: "HTTP/1.1", headerFields: nil) + let dataResponse = AFDataResponse(request: URLRequest(url: url), response: response, data: body, metrics: nil, serializationDuration: 0, result: .failure(.responseValidationFailed(reason: .unacceptableStatusCode(code: 400)))) - #expect(error.localizedGenericMessage == "Password needs to be at least 10 characters long") - } - - @Test func ignoresTechnicalServerMessage() { - let body = Data(#"Sabre\DAV\Exception\ConflictParent node does not exist"#.utf8) - let error = NKError(errorCode: 409, errorDescription: "Parent node does not exist", responseData: body) - - #expect(error.localizedGenericMessage != "Parent node does not exist") - #expect(error.localizedGenericMessage == NKError(errorCode: 409).localizedGenericMessage) + #expect(NKError(error: dataResponse.error, afResponse: dataResponse).localizedGenericMessage == "Password needs to be at least 10 characters long") } } From 4ac49ab5fa4e5f3b112afff7658713d52dbaf593 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 20 Oct 2026 14:50:40 +0200 Subject: [PATCH 7/9] WIP Signed-off-by: Milen Pivchev --- AGENTS.md | 2 +- Sources/NextcloudKit/Localizable.xcstrings | 477 +-- Sources/NextcloudKit/NKError+Kind.swift | 4 +- .../NKError+localizedGenericMessage.swift | 2 +- Sources/NextcloudKit/NKError.swift | 108 +- Sources/NextcloudKit/NextcloudKit+Login.swift | 4 +- .../Utils/FileNameValidator.swift | 2 +- Sources/NextcloudKitUI/Localizable.xcstrings | 3329 +++++++++-------- .../NKErrorLocalizedGenericMessageTests.swift | 2 - 9 files changed, 1862 insertions(+), 2068 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0190b6e9..5cbc0db4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Networking is built on Alamofire; responses are parsed with SwiftyJSON and Swift | Directory | Description | Agent Action | |-----------------|-----------------------------------------------------|----------------------| | `.github` | GitHub CI workflows (build & test, lint, REUSE compliance, documentation). | Keep all four checks green: build & test (`xcode.yml`), SwiftLint (`lint.yml`), REUSE compliance (`reuse.yml`), DocC build (`documentation.yml`). | -| `Sources/NextcloudKit` | Core library. Server APIs live in one `NextcloudKit+.swift` extension file per API area (e.g. `NextcloudKit+Share.swift`), next to core types like `NextcloudKit`, `NKCommon`, `NKSession` and `NKError`. Shared helpers live in `Extensions/`, `Log/`, `Utils/` and `TypeIdentifiers/`. `Localizable.xcstrings` holds the user-facing error messages (`NKError.localizedGenericMessage`). | Add new server APIs as a dedicated `NextcloudKit+.swift` extension file. Only add new localized strings to `Localizable.xcstrings` in English, with `NSLocalizedString(…, bundle: .module, comment:)` since `String(localized:)` needs newer deployment targets. | +| `Sources/NextcloudKit` | Core library. Server APIs live in one `NextcloudKit+.swift` extension file per API area (e.g. `NextcloudKit+Share.swift`), next to core types like `NextcloudKit`, `NKCommon`, `NKSession` and `NKError`. Shared helpers live in `Extensions/`, `Log/`, `Utils/` and `TypeIdentifiers/`. `Localizable.xcstrings` holds the user-facing strings: `NKError.localizedGenericMessage`, file name rule messages and log level names. NKError's own descriptions (the presets and `getErrorDescription(for:)`) stay plain English, for logs. | Add new server APIs as a dedicated `NextcloudKit+.swift` extension file. Only add new localized strings to `Localizable.xcstrings` in English, with `NSLocalizedString(…, bundle: .module, comment:)` since `String(localized:)` needs newer deployment targets. | | `Sources/NextcloudKit/Models` | `NK*` data models returned by the APIs. | Place each new model in its own file; group related models for one feature in a subfolder (see `Models/Governance`). | | `Sources/NextcloudKitUI` | SwiftUI login and account UI used by client apps. Depends on CodeScanner for QR-code login. | Only add new localized strings to `Localizable.xcstrings` in English. | | `Tests/NextcloudKitUnitTests` | Unit tests; network requests are mocked with Mocker (see `LoginUnitTests.swift`). | Prefer this target for new tests. Try to add unit tests for new features, where applicable and makes sense; do not overcomplicate them. | diff --git a/Sources/NextcloudKit/Localizable.xcstrings b/Sources/NextcloudKit/Localizable.xcstrings index 971076cd..4d5f5d87 100644 --- a/Sources/NextcloudKit/Localizable.xcstrings +++ b/Sources/NextcloudKit/Localizable.xcstrings @@ -12,13 +12,13 @@ } } }, - ".\"%@\" is a forbidden file extension." : { + "\".%@\" is a forbidden file extension." : { "comment" : "Error message when the server forbids a file extension. %@ is the extension.", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : ".\"%@\" is a forbidden file extension." + "value" : "\".%@\" is a forbidden file extension." } } } @@ -45,28 +45,6 @@ } } }, - "Bad gateway" : { - "comment" : "Error message when a proxy got an invalid response from the server (HTTP 502).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bad gateway" - } - } - } - }, - "Bad request" : { - "comment" : "Error message when the server cannot process the request (HTTP 400).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bad request" - } - } - } - }, "Can't reach the server. Check the address or try again later." : { "comment" : "Error message when the server cannot be reached.", "localizations" : { @@ -78,17 +56,6 @@ } } }, - "Cancelled" : { - "comment" : "Error message when a request was cancelled.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancelled" - } - } - } - }, "Compact" : { "comment" : "Log level that logs only the essentials.", "localizations" : { @@ -100,28 +67,6 @@ } } }, - "Connection SSL error, try again" : { - "comment" : "Error message when a secure connection to the server failed.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection SSL error, try again" - } - } - } - }, - "Create folder error" : { - "comment" : "Error message when the folder for a chunked upload cannot be created.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Create folder error" - } - } - } - }, "Disabled" : { "comment" : "Log level that turns logging off.", "localizations" : { @@ -133,17 +78,6 @@ } } }, - "Expectation failed" : { - "comment" : "Error message when the server cannot meet the request's expectation (HTTP 417).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Expectation failed" - } - } - } - }, "File name cannot be empty." : { "comment" : "Error message when a file or folder name is empty.", "localizations" : { @@ -155,160 +89,6 @@ } } }, - "File not found" : { - "comment" : "Error message when the file to upload in chunks cannot be found.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File not found" - } - } - } - }, - "Files not found" : { - "comment" : "Error message when a chunked upload has no chunks to upload.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Files not found" - } - } - } - }, - "Forbidden" : { - "comment" : "Error message when the server forbids the request.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forbidden" - } - } - } - }, - "Gateway timeout" : { - "comment" : "Error message when a proxy timed out waiting for the server (HTTP 504).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gateway timeout" - } - } - } - }, - "Generic error" : { - "comment" : "Error message when no more specific description is available.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Generic error" - } - } - } - }, - "Internal error" : { - "comment" : "Error message for an internal error in the app.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Internal error" - } - } - } - }, - "Internal server error" : { - "comment" : "Error message when the server ran into an error (HTTP 500).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Internal server error" - } - } - } - }, - "Invalid data format" : { - "comment" : "Error message when data in the server's response cannot be read.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid data format" - } - } - } - }, - "Invalid date format" : { - "comment" : "Error message when a date in the server's response cannot be read.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid date format" - } - } - } - }, - "Invalid response" : { - "comment" : "Error message when the server's response is not valid.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid response" - } - } - } - }, - "Invalid response, error decoding XML" : { - "comment" : "Error message when the server's XML response cannot be read.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid response, error decoding XML" - } - } - } - }, - "Invalid server URL" : { - "comment" : "Error message when the server address is not a valid URL.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid server URL" - } - } - } - }, - "It is not possible to connect to the server at this time" : { - "comment" : "Error message when the connection to the server failed.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "It is not possible to connect to the server at this time" - } - } - } - }, - "Move file error" : { - "comment" : "Error message when the uploaded chunks cannot be assembled into the file.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Move file error" - } - } - } - }, "Name contains an invalid character: \"%@\"." : { "comment" : "Error message when a name contains a forbidden character. %@ is the character.", "localizations" : { @@ -342,61 +122,6 @@ } } }, - "Not enough memory" : { - "comment" : "Error message when the device lacks the space to split a file into chunks.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not enough memory" - } - } - } - }, - "Request timeout" : { - "comment" : "Error message when the server timed out waiting for the request (HTTP 408).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Request timeout" - } - } - } - }, - "Resource not modified" : { - "comment" : "Error message when the resource was not modified (HTTP 304).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resource not modified" - } - } - } - }, - "Server connection error" : { - "comment" : "Error message when the device has no internet connection.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server connection error" - } - } - } - }, - "Server is currently in maintenance mode" : { - "comment" : "Error message when the server is in maintenance mode (HTTP 503).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server is currently in maintenance mode" - } - } - } - }, "Something went wrong. Please try again." : { "comment" : "Error message when the cause of the error is unknown.", "localizations" : { @@ -408,28 +133,6 @@ } } }, - "Storage quota is reached" : { - "comment" : "Error message when the account's storage quota is used up (HTTP 507).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Storage quota is reached" - } - } - } - }, - "The certificate for this server is invalid" : { - "comment" : "Error message when the server's certificate is not trusted.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The certificate for this server is invalid" - } - } - } - }, "The file is locked. Try again later." : { "comment" : "Error message when the file is locked on the server.", "localizations" : { @@ -441,17 +144,6 @@ } } }, - "The file is too large" : { - "comment" : "Error message when the file is too large for the server (HTTP 413).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file is too large" - } - } - } - }, "The file is too large for the server." : { "comment" : "Error message when the server rejects a file because of its size.", "localizations" : { @@ -485,28 +177,6 @@ } } }, - "The name contains at least one invalid character" : { - "comment" : "Error message when the server rejects a name because of invalid characters.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The name contains at least one invalid character" - } - } - } - }, - "The request could not be completed due to a conflict with the current state of the resource" : { - "comment" : "Error message when the request conflicts with the current state on the server (HTTP 409).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The request could not be completed due to a conflict with the current state of the resource" - } - } - } - }, "The request was cancelled." : { "comment" : "Error message when a request was cancelled.", "localizations" : { @@ -518,50 +188,6 @@ } } }, - "The requested method is not supported" : { - "comment" : "Error message when the server doesn't allow the request method (HTTP 405).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The requested method is not supported" - } - } - } - }, - "The requested resource could not be found" : { - "comment" : "Error message when the file, folder or resource does not exist (HTTP 404).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The requested resource could not be found" - } - } - } - }, - "The server appears to be down" : { - "comment" : "Error message when the server cannot be reached.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server appears to be down" - } - } - } - }, - "The server does not meet one of the preconditions of the request" : { - "comment" : "Error message when a precondition of the request failed (HTTP 412).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server does not meet one of the preconditions of the request" - } - } - } - }, "The server is temporarily unavailable. Try again later." : { "comment" : "Error message when the server is unavailable, e.g. in maintenance mode.", "localizations" : { @@ -639,28 +265,6 @@ } } }, - "Time out" : { - "comment" : "Error message when a request timed out.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Time out" - } - } - } - }, - "Too many requests" : { - "comment" : "Error message when the server rate-limits the requests (HTTP 429).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Too many requests" - } - } - } - }, "Too many requests. Please wait a moment and try again." : { "comment" : "Error message when the server rate-limits the requests.", "localizations" : { @@ -672,50 +276,6 @@ } } }, - "Transfer stopped" : { - "comment" : "Error message when a transfer was stopped.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Transfer stopped" - } - } - } - }, - "Unauthorized" : { - "comment" : "Error message when the server rejects the login (HTTP 401).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unauthorized" - } - } - } - }, - "Unavailable" : { - "comment" : "Error message when the server is unavailable.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unavailable" - } - } - } - }, - "User authentication required" : { - "comment" : "Error message when the server requires a login.", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "User authentication required" - } - } - } - }, "Verbose" : { "comment" : "Log level that logs everything, for debugging.", "localizations" : { @@ -727,39 +287,6 @@ } } }, - "WebDAV Locked: Trying to access locked resource" : { - "comment" : "Error message when the file is locked (HTTP 423).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "WebDAV Locked: Trying to access locked resource" - } - } - } - }, - "WebDAV multistatus" : { - "comment" : "Error message for a WebDAV Multi-Status response (HTTP 207).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "WebDAV multistatus" - } - } - } - }, - "You don't have permission to complete the operation" : { - "comment" : "Error message when the server forbids the operation (HTTP 403).", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You don't have permission to complete the operation" - } - } - } - }, "You don't have permission to do this." : { "comment" : "Error message when the server forbids the action.", "localizations" : { diff --git a/Sources/NextcloudKit/NKError+Kind.swift b/Sources/NextcloudKit/NKError+Kind.swift index 4a9d3401..c0c2f353 100644 --- a/Sources/NextcloudKit/NKError+Kind.swift +++ b/Sources/NextcloudKit/NKError+Kind.swift @@ -86,7 +86,7 @@ extension NKError.Kind { } extension NKError.Kind { - init(urlErrorCode code: Int) { + private init(urlErrorCode code: Int) { switch code { case NSURLErrorCancelled: self = .cancelled @@ -122,7 +122,7 @@ extension NKError.Kind { } } - init(httpStatusCode: Int, method: String? = nil) { + private init(httpStatusCode: Int, method: String? = nil) { let method = method?.uppercased() switch httpStatusCode { diff --git a/Sources/NextcloudKit/NKError+localizedGenericMessage.swift b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift index e7dabf1e..f852918b 100644 --- a/Sources/NextcloudKit/NKError+localizedGenericMessage.swift +++ b/Sources/NextcloudKit/NKError+localizedGenericMessage.swift @@ -5,7 +5,7 @@ import Foundation public extension NKError { - /// User friendly messages for generic errors. Each one fits a `kind`. + /// The message to show users: the share API's own message when there is one, otherwise a translated message for ``kind``. var localizedGenericMessage: String { if let userFriendlyServerMessage { return userFriendlyServerMessage diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index 84454027..6ec99bbd 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -45,25 +45,25 @@ public struct NKError: Error, Equatable, Sendable { /// What went wrong, for choosing the message shown to users; `nil` for ``success``. public let kind: Kind? /// The OCS message the server sent with the error. - let serverMessage: String? + private let serverMessage: String? /// The URL path of the request that failed, when known. - private(set) var requestPath: String? - - public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: NSLocalizedString("Invalid server URL", bundle: .module, comment: "Error message when the server address is not a valid URL.")) - public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid response", bundle: .module, comment: "Error message when the server's response is not valid.")) - public static let xmlError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid response, error decoding XML", bundle: .module, comment: "Error message when the server's XML response cannot be read.")) - public static let invalidDate = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: NSLocalizedString("Invalid date format", bundle: .module, comment: "Error message when a date in the server's response cannot be read.")) - public static let invalidData = NKError(errorCode: NSURLErrorCannotDecodeContentData, errorDescription: NSLocalizedString("Invalid data format", bundle: .module, comment: "Error message when data in the server's response cannot be read.")) - public static let unauthorizedError = NKError(errorCode: 401, errorDescription: NSLocalizedString("Unauthorized", bundle: .module, comment: "Error message when the server rejects the login (HTTP 401).")) - public static let unavailableError = NKError(errorCode: 503, errorDescription: NSLocalizedString("Unavailable", bundle: .module, comment: "Error message when the server is unavailable.")) - public static let forbiddenError = NKError(errorCode: 403, errorDescription: NSLocalizedString("Forbidden", bundle: .module, comment: "Error message when the server forbids the request.")) - public static let cancelled = NKError(errorCode: -999, errorDescription: NSLocalizedString("Cancelled", bundle: .module, comment: "Error message when a request was cancelled.")) - - public static let errorChunkFileNull = NKError(errorCode: -9994, errorDescription: NSLocalizedString("File not found", bundle: .module, comment: "Error message when the file to upload in chunks cannot be found.")) - public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: NSLocalizedString("Files not found", bundle: .module, comment: "Error message when a chunked upload has no chunks to upload.")) - public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: NSLocalizedString("Create folder error", bundle: .module, comment: "Error message when the folder for a chunked upload cannot be created.")) - public static let errorChunkMoveFile = NKError(errorCode: -9997, errorDescription: NSLocalizedString("Move file error", bundle: .module, comment: "Error message when the uploaded chunks cannot be assembled into the file.")) - public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: NSLocalizedString("Not enough memory", bundle: .module, comment: "Error message when the device lacks the space to split a file into chunks."), kind: .localStorageFull) + private var requestPath: String? + + public static let urlError = NKError(errorCode: NSURLErrorBadURL, errorDescription: "Invalid server URL") + public static let invalidResponseError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: "Invalid response") + public static let xmlError = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: "Invalid response, error decoding XML") + public static let invalidDate = NKError(errorCode: NSURLErrorBadServerResponse, errorDescription: "Invalid date format") + public static let invalidData = NKError(errorCode: NSURLErrorCannotDecodeContentData, errorDescription: "Invalid data format") + public static let unauthorizedError = NKError(errorCode: 401, errorDescription: "Unauthorized") + public static let unavailableError = NKError(errorCode: 503, errorDescription: "Unavailable") + public static let forbiddenError = NKError(errorCode: 403, errorDescription: "Forbidden") + public static let cancelled = NKError(errorCode: -999, errorDescription: "Cancelled") + + public static let errorChunkFileNull = NKError(errorCode: -9994, errorDescription: "File not found") + public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: "Files not found") + public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: "Create folder error") + public static let errorChunkMoveFile = NKError(errorCode: -9997, errorDescription: "Move file error") + public static let errorChunkNoEnoughMemory = NKError(errorCode: -9998, errorDescription: "Not enough memory", kind: .localStorageFull) public static let success = NKError(errorCode: 0, errorDescription: "") @@ -74,110 +74,110 @@ public struct NKError: Error, Equatable, Sendable { return serverMessage } - /// Returns a localized user-facing description for a known error code. + /// Returns the English description of a known error code, for logs. /// /// - Parameter code: The HTTP, URL loading, WebDAV, OCS, or internal error code. - /// - Returns: A localized description when the code is known, otherwise `nil`. + /// - Returns: A description when the code is known, otherwise `nil`. public static func getErrorDescription(for code: Int) -> String? { switch code { case -9999: - return NSLocalizedString("Internal error", bundle: .module, comment: "Error message for an internal error in the app.") + return "Internal error" case -1001: - return NSLocalizedString("Time out", bundle: .module, comment: "Error message when a request timed out.") + return "Time out" case -1004: - return NSLocalizedString("The server appears to be down", bundle: .module, comment: "Error message when the server cannot be reached.") + return "The server appears to be down" case -1005: - return NSLocalizedString("It is not possible to connect to the server at this time", bundle: .module, comment: "Error message when the connection to the server failed.") + return "It is not possible to connect to the server at this time" case -1009: - return NSLocalizedString("Server connection error", bundle: .module, comment: "Error message when the device has no internet connection.") + return "Server connection error" case -1011: - return NSLocalizedString("Generic error", bundle: .module, comment: "Error message when no more specific description is available.") + return "Generic error" case -1012: - return NSLocalizedString("It is not possible to connect to the server at this time", bundle: .module, comment: "Error message when the connection to the server failed.") + return "It is not possible to connect to the server at this time" case -1013: - return NSLocalizedString("User authentication required", bundle: .module, comment: "Error message when the server requires a login.") + return "User authentication required" case -1200: - return NSLocalizedString("Connection SSL error, try again", bundle: .module, comment: "Error message when a secure connection to the server failed.") + return "Connection SSL error, try again" case -1202: - return NSLocalizedString("The certificate for this server is invalid", bundle: .module, comment: "Error message when the server's certificate is not trusted.") + return "The certificate for this server is invalid" case 0: return "" case 101: - return NSLocalizedString("The name contains at least one invalid character", bundle: .module, comment: "Error message when the server rejects a name because of invalid characters.") + return "The name contains at least one invalid character" case 200: - return NSLocalizedString("Transfer stopped", bundle: .module, comment: "Error message when a transfer was stopped.") + return "Transfer stopped" case 207: - return NSLocalizedString("WebDAV multistatus", bundle: .module, comment: "Error message for a WebDAV Multi-Status response (HTTP 207).") + return "WebDAV multistatus" case 304: - return NSLocalizedString("Resource not modified", bundle: .module, comment: "Error message when the resource was not modified (HTTP 304).") + return "Resource not modified" case 400: - return NSLocalizedString("Bad request", bundle: .module, comment: "Error message when the server cannot process the request (HTTP 400).") + return "Bad request" case 401: - return NSLocalizedString("Unauthorized", bundle: .module, comment: "Error message when the server rejects the login (HTTP 401).") + return "Unauthorized" case 403: - return NSLocalizedString("You don't have permission to complete the operation", bundle: .module, comment: "Error message when the server forbids the operation (HTTP 403).") + return "You don't have permission to complete the operation" case 404: - return NSLocalizedString("The requested resource could not be found", bundle: .module, comment: "Error message when the file, folder or resource does not exist (HTTP 404).") + return "The requested resource could not be found" case 405: - return NSLocalizedString("The requested method is not supported", bundle: .module, comment: "Error message when the server doesn't allow the request method (HTTP 405).") + return "The requested method is not supported" case 408: - return NSLocalizedString("Request timeout", bundle: .module, comment: "Error message when the server timed out waiting for the request (HTTP 408).") + return "Request timeout" case 409: - return NSLocalizedString("The request could not be completed due to a conflict with the current state of the resource", bundle: .module, comment: "Error message when the request conflicts with the current state on the server (HTTP 409).") + return "The request could not be completed due to a conflict with the current state of the resource" case 412: - return NSLocalizedString("The server does not meet one of the preconditions of the request", bundle: .module, comment: "Error message when a precondition of the request failed (HTTP 412).") + return "The server does not meet one of the preconditions of the request" case 413: - return NSLocalizedString("The file is too large", bundle: .module, comment: "Error message when the file is too large for the server (HTTP 413).") + return "The file is too large" case 417: - return NSLocalizedString("Expectation failed", bundle: .module, comment: "Error message when the server cannot meet the request's expectation (HTTP 417).") + return "Expectation failed" case 423: - return NSLocalizedString("WebDAV Locked: Trying to access locked resource", bundle: .module, comment: "Error message when the file is locked (HTTP 423).") + return "WebDAV Locked: Trying to access locked resource" case 429: - return NSLocalizedString("Too many requests", bundle: .module, comment: "Error message when the server rate-limits the requests (HTTP 429).") + return "Too many requests" case 500: - return NSLocalizedString("Internal server error", bundle: .module, comment: "Error message when the server ran into an error (HTTP 500).") + return "Internal server error" case 502: - return NSLocalizedString("Bad gateway", bundle: .module, comment: "Error message when a proxy got an invalid response from the server (HTTP 502).") + return "Bad gateway" case 503: - return NSLocalizedString("Server is currently in maintenance mode", bundle: .module, comment: "Error message when the server is in maintenance mode (HTTP 503).") + return "Server is currently in maintenance mode" case 504: - return NSLocalizedString("Gateway timeout", bundle: .module, comment: "Error message when a proxy timed out waiting for the server (HTTP 504).") + return "Gateway timeout" case 507: - return NSLocalizedString("Storage quota is reached", bundle: .module, comment: "Error message when the account's storage quota is used up (HTTP 507).") + return "Storage quota is reached" case NSURLErrorCannotDecodeContentData: - return NSLocalizedString("Invalid data format", bundle: .module, comment: "Error message when data in the server's response cannot be read.") + return "Invalid data format" default: return nil @@ -195,7 +195,7 @@ public struct NKError: Error, Equatable, Sendable { let description = HTTPURLResponse.localizedString(forStatusCode: statusCode) if description.isEmpty { - return NSLocalizedString("Generic error", bundle: .module, comment: "Error message when no more specific description is available.") + return "Generic error" } return description @@ -211,7 +211,7 @@ public struct NKError: Error, Equatable, Sendable { /// /// - Parameters: /// - errorCode: The error code. - /// - errorDescription: The user-facing error description. + /// - errorDescription: The error description, mostly for logs. /// - responseData: Optional raw response data associated with the error. /// - kind: The kind of failure; derived from `errorCode` when `nil`. public init(errorCode: Int = 0, errorDescription: String = "", responseData: Data? = nil, kind: Kind? = nil) { diff --git a/Sources/NextcloudKit/NextcloudKit+Login.swift b/Sources/NextcloudKit/NextcloudKit+Login.swift index a4c0114a..240f6a99 100644 --- a/Sources/NextcloudKit/NextcloudKit+Login.swift +++ b/Sources/NextcloudKit/NextcloudKit+Login.swift @@ -286,12 +286,12 @@ public extension NextcloudKit { } guard let endpointString, let endpointURL = URL(string: endpointString) else { - continuation.resume(throwing: NKError.urlError) + continuation.resume(throwing: NKError.invalidResponseError) return } guard let loginString, let loginURL = URL(string: loginString) else { - continuation.resume(throwing: NKError.urlError) + continuation.resume(throwing: NKError.invalidResponseError) return } diff --git a/Sources/NextcloudKit/Utils/FileNameValidator.swift b/Sources/NextcloudKit/Utils/FileNameValidator.swift index 100ca017..e35bd7c3 100644 --- a/Sources/NextcloudKit/Utils/FileNameValidator.swift +++ b/Sources/NextcloudKit/Utils/FileNameValidator.swift @@ -26,7 +26,7 @@ public final class FileNameValidator: Sendable { } public func fileForbiddenFileExtensionError(templateString: String) -> NKError { - let errorMessageTemplate = NSLocalizedString(".\"%@\" is a forbidden file extension.", bundle: .module, comment: "Error message when the server forbids a file extension. %@ is the extension.") + let errorMessageTemplate = NSLocalizedString("\".%@\" is a forbidden file extension.", bundle: .module, comment: "Error message when the server forbids a file extension. %@ is the extension.") let errorMessage = String(format: errorMessageTemplate, templateString) return NKError(errorCode: NSURLErrorCannotCreateFile, errorDescription: errorMessage) } diff --git a/Sources/NextcloudKitUI/Localizable.xcstrings b/Sources/NextcloudKitUI/Localizable.xcstrings index a8df654b..3527724c 100644 --- a/Sources/NextcloudKitUI/Localizable.xcstrings +++ b/Sources/NextcloudKitUI/Localizable.xcstrings @@ -1,9 +1,6 @@ { "sourceLanguage" : "en", "strings" : { - "" : { - - }, "%lld groups" : { "comment" : "Recipient summary for multiple groups.", "localizations" : { @@ -85,6 +82,12 @@ "value" : "%lld skupín" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld grupper" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -186,6 +189,12 @@ "value" : "%lld odkazov" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld länkar" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -287,6 +296,12 @@ "value" : "%lld ľudí" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld personer" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -388,6 +403,12 @@ "value" : "1 skupina" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 grupp" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -489,6 +510,12 @@ "value" : "1 odkaz" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 länk" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -584,6 +611,12 @@ "value" : "1 osoba" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 person" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -667,6 +700,12 @@ "value" : "Сметки" } }, + "mn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бүртгэлүүд" + } + }, "pt_BR" : { "stringUnit" : { "state" : "translated", @@ -685,6 +724,12 @@ "value" : "Účty" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konton" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -786,6 +831,12 @@ "value" : "Účty z iných aplikácií" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konton från andra appar" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -1164,407 +1215,431 @@ } } }, - "Anyone" : { - "comment" : "Audience option that makes a unified share accessible to anyone with its public link.", + "Apply permissions to all recipients?" : { + "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", "localizations" : { - "af" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Enige iemand" + "value" : "Anvend rettighederne på alle modtagere?" } }, - "bn_BD" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "যে কেউ" + "value" : "Berechtigungen auf alle Empfänger anwenden" } }, - "cs_CZ" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Kdokoli" + "value" : "Apply permissions to all recipients?" } }, - "da" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Alle" + "value" : "Apply permissions to all recipients?" } }, - "de" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Jeder" + "value" : "¿Aplicar permisos a todos los destinatarios?" } }, - "el" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Οποιοσδήποτε" + "value" : "Kas rakendad õigusi kõikidele saajatele?" } }, - "en" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Anyone" + "value" : "Appliquer les autorisations à tous les destinataires ?" } }, - "en_GB" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Anyone" + "value" : "Cuir ceadanna i bhfeidhm ar gach faighteoir?" } }, - "es" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cualquiera" + "value" : "Applicare le autorizzazioni a tutti i destinatari?" } }, - "et_EE" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Kõik" + "value" : "Aplicar permissões a todos os destinatários?" } }, - "eu" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Edonor" + "value" : "Применить разрешения ко всем получателям?" } }, - "fa" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "هرکسی" + "value" : "Použiť oprávnenia na všetkých príjemcov?" } }, - "fi_FI" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kuka tahansa" + "value" : "Tillämpa behörigheterna för alla mottagare?" } }, - "fr" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tout le monde" + "value" : "İzinler tüm alıcılara uygulansın mı?" } }, - "ga" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Aon duine" + "value" : "要將權限套用至所有接收者嗎?" } }, - "he" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "כל אחד" + "value" : "套用權限至所有收件人?" } - }, - "hu_HU" : { + } + } + }, + "Apply to all" : { + "comment" : "Button that confirms applying top-level permissions to every recipient.", + "localizations" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Bárki" + "value" : "Anvend på alle" } }, - "id" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Siapapun" + "value" : "Auf alle anwenden" } }, - "is" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Hver sem er" + "value" : "Apply to all" } }, - "it" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Chiunque" + "value" : "Apply to all" } }, - "ja_JP" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "誰でも" + "value" : "Aplicar a todos" } }, - "lt_LT" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Bet kas" + "value" : "Rakenda kõigile" } }, - "nl" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Iedereen" + "value" : "Appliquer à tous" } }, - "pl" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Dowolna osoba" + "value" : "Cuir i bhfeidhm ar gach rud" } }, - "pt_BR" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Qualquer pessoa" + "value" : "Applicare a tutti" } }, - "pt_PT" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Qualquer um" + "value" : "Aplicar a todos" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Кто угодно" + "value" : "Применить ко всем" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Ktokoľvek" + "value" : "Použiť na všetko" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Vem som helst" + "value" : "Tillämpa för alla" } }, - "sw" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Mtu yeyote" + "value" : "Tümüne uygula" } }, - "tr" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Herkes" + "value" : "套用至全部" } }, - "uk" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Будь-хто" + "value" : "套用至全部" } - }, - "zh_CN" : { + } + } + }, + "Anyone" : { + "comment" : "Audience option that makes a unified share accessible to anyone with its public link.", + "localizations" : { + "af" : { "stringUnit" : { "state" : "translated", - "value" : "任何人" + "value" : "Enige iemand" } }, - "zh_HK" : { + "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "任何人" + "value" : "যে কেউ" } }, - "zh_TW" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "任何人" + "value" : "Kdokoli" } - } - } - }, - "Apply permissions to all recipients?" : { - "comment" : "Confirmation title shown before replacing every recipient's individual permissions.", - "localizations" : { + }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Anvend rettighederne på alle modtagere?" + "value" : "Alle" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Berechtigungen auf alle Empfänger anwenden" + "value" : "Jeder" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Οποιοσδήποτε" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Apply permissions to all recipients?" + "value" : "Anyone" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Apply permissions to all recipients?" + "value" : "Anyone" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "¿Aplicar permisos a todos los destinatarios?" + "value" : "Cualquiera" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kas rakendad õigusi kõikidele saajatele?" + "value" : "Kõik" } }, - "fr" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Appliquer les autorisations à tous les destinataires ?" + "value" : "Edonor" } }, - "ga" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Cuir ceadanna i bhfeidhm ar gach faighteoir?" + "value" : "هرکسی" } }, - "it" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Applicare le autorizzazioni a tutti i destinatari?" + "value" : "Kuka tahansa" } }, - "pt_BR" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aplicar permissões a todos os destinatários?" + "value" : "Tout le monde" } }, - "ru" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Применить разрешения ко всем получателям?" + "value" : "Aon duine" } }, - "sk_SK" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť oprávnenia na všetkých príjemcov?" + "value" : "כל אחד" } }, - "tr" : { + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "İzinler tüm alıcılara uygulansın mı?" + "value" : "Bárki" } }, - "zh_HK" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "要將權限套用至所有接收者嗎?" + "value" : "Siapapun" } }, - "zh_TW" : { + "is" : { "stringUnit" : { "state" : "translated", - "value" : "套用權限至所有收件人?" + "value" : "Hver sem er" } - } - } - }, - "Apply to all" : { - "comment" : "Button that confirms applying top-level permissions to every recipient.", - "localizations" : { - "da" : { + }, + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Anvend på alle" + "value" : "Chiunque" } }, - "de" : { + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Auf alle anwenden" + "value" : "誰でも" } }, - "en" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Apply to all" + "value" : "Bet kas" } }, - "en_GB" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Apply to all" + "value" : "Хэн ч бай" } }, - "es" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aplicar a todos" + "value" : "Iedereen" } }, - "et_EE" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rakenda kõigile" + "value" : "Dowolna osoba" } }, - "fr" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Appliquer à tous" + "value" : "Qualquer pessoa" } }, - "ga" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Cuir i bhfeidhm ar gach rud" + "value" : "Qualquer um" } }, - "it" : { + "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Applicare a tutti" + "value" : "Oricine" } }, - "pt_BR" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Aplicar a todos" + "value" : "Кто угодно" } }, - "ru" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Применить ко всем" + "value" : "Ktokoľvek" } }, - "sk_SK" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť na všetko" + "value" : "Vem som helst" + } + }, + "sw" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mtu yeyote" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tümüne uygula" + "value" : "Herkes" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь-хто" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "任何人" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "套用至全部" + "value" : "任何人" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "套用至全部" + "value" : "任何人" } } } @@ -2210,1669 +2285,1693 @@ } } }, - "Copied" : { - "comment" : "Temporary button label confirming that a share link was copied.", + "Custom permissions" : { + "comment" : "Menu option that reveals per-permission toggles for custom permissions.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نُسخت " + "value" : "أذونات مخصصة" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Copióse" + "value" : "Personlizar los permisos" } }, - "be" : { + "az" : { "stringUnit" : { "state" : "translated", - "value" : "Скапіявана" + "value" : "Xüsusi icazələr" + } + }, + "bg_BG" : { + "stringUnit" : { + "state" : "translated", + "value" : "Персонализиране на права" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Permisos personalitzats" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírováno" + "value" : "Uživatelsky určená oprávnění" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieret" + "value" : "Brugerdefinerede rettigheder" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiert" + "value" : "Benutzerdefinierte Berechtigungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αντιγράφηκε" + "value" : "Προσαρμοσμένα δικαιώματα" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Custom permissions" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "Custom permissions" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Permisos personalizados" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Permisos personalizados" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" - } - }, - "es_PE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copiado" + "value" : "Permisos personalizados" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeritud" + "value" : "Kohandatud õigused" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatuta" + "value" : "Baimen pertsonalizatuak" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "کپی شد" + "value" : "مجوزهای سفارشی" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioitu" + "value" : "Mukautetut oikeudet" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copié" + "value" : "Autorisations personnalisées" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáladh" + "value" : "Ceadanna saincheaptha" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Permisos personalizados" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopirano" + "value" : "Prilagođena dopuštenja" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Másolva" + "value" : "Egyéni jogosultságok" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Disalin" + "value" : "Izin kustom" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Afritað" + "value" : "Sérsniðnar heimildir" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copiato" + "value" : "Autorizzazioni personalizzate" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "コピー" + "value" : "カスタム権限" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" - } - }, - "kab" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yettwanɣel" + "value" : "Custom permissions" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "복사됨" + "value" : "사용자 지정 권한" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Copied" + "value" : "ສິດອະນຸຍາດແບບກຳນົດເອງ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Nukopijuota" + "value" : "Tinkinti leidimai" } }, - "mn" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Хуулагдсан" + "value" : "Прилагодени дозволи" } }, - "nb_NO" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiert" + "value" : "Тусгай эрхүүд" } }, - "nl" : { + "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Gekopieerd" + "value" : "Egendefinerte tillatelser" } }, - "oc" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Aangepaste machtigingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Skopiowane" + "value" : "Uprawnienia niestandardowe" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Permissões personalizadas" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Copiado" + "value" : "Permissões personalizadas" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Copiat" + "value" : "Permisiuni particularizate" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировано" + "value" : "Пользовательские права доступа" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiadu" + "value" : "Permissos personalizados" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Skopírované" + "value" : "Vlastné oprávnenia" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopirano" + "value" : "Dovoljenja po meri" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Копирано" + "value" : "Произвољне дозволе" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kopierat" + "value" : "Anpassade behörigheter" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Iliyonakiliwa" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "நகலெடுக்கப்பட்டது" + "value" : "Ruhusa maalum" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopyalandı" + "value" : "Özel izinler" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "كۆچۈرۈلگەن" + "value" : "ئىختىيارى ئىجازەت" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Скопійовано" + "value" : "Спеціальні дозволи" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Đã sao chép" + "value" : "Quyền tùy chỉnh" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "已复制" + "value" : "自定义权限" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "已複製" + "value" : "自訂權限" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "已複製" + "value" : "自訂權限" } } } }, - "Copy private link" : { - "comment" : "Copies the in-server link of the file for invited people.", + "Copied" : { + "comment" : "Temporary button label confirming that a share link was copied.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نسخ الرابط الخاص" + "value" : "نُسخت " } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar l'enllaz priváu" + "value" : "Copióse" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Капіяваць прыватную спасылку" + "value" : "Скапіявана" } }, - "bg_BG" : { + "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Копирай скритата връзка" + "value" : "Copiat" } }, - "br" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Eilañ al liamm prevez" + "value" : "Zkopírováno" } }, - "ca" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Copia l'enllaç privat" + "value" : "Kopieret" } }, - "cs_CZ" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírovat soukromý odkaz" + "value" : "Kopiert" } }, - "cy_GB" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "Copïo dolen breifat" + "value" : "Αντιγράφηκε" } }, - "da" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiér privat link" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Privaten Link kopieren" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "Αντιγραφή ιδιωτικού συνδέσμου" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copy private link" + "value" : "Copied" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copied" } }, - "eo" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Kopii privatan ligilon" + "value" : "Copiado" } }, - "es" : { + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiado" } }, - "es_EC" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiado" } }, - "es_MX" : { + "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace privado" + "value" : "Copiado" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeri privaatne link" + "value" : "Kopeeritud" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatu esteka pribatua" + "value" : "Kopiatuta" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "کپی پیوند خصوصی" + "value" : "کپی شد" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioi yksityinen linkki" + "value" : "Kopioitu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier le lien privé" + "value" : "Copié" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáil nasc príobháideach" + "value" : "Cóipeáladh" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar a ligazón privada" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "העתקת קישור פרטי" + "value" : "Copiado" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj privatnu poveznicu" + "value" : "Kopirano" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Személyes hivatkozás másolása" + "value" : "Másolva" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Salin tautan privat" + "value" : "Disalin" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Afrita einkatengil" + "value" : "Afritað" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia collegamento privato" + "value" : "Copiato" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "プライベートリンクをコピー" + "value" : "コピー" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copied" + } + }, + "kab" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yettwanɣel" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "개인 링크 주소 복사" + "value" : "복사됨" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Copy private link" + "value" : "Copied" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Kopijuoti privačią nuorodą" + "value" : "Nukopijuota" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај приватен линк" + "value" : "Копирано" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Хувийн холбоос хуулах" + "value" : "Хуулагдсан" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier privat lenke" + "value" : "Kopiert" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Privélink kopiëren" + "value" : "Gekopieerd" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar lo ligam privat" + "value" : "Copiat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj link prywatny" + "value" : "Skopiowane" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link privado" + "value" : "Copiado" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link privado" + "value" : "Copiado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Copiază link privat" + "value" : "Copiat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировать закрытую ссылку" + "value" : "Скопировано" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Còpia ligòngiu privadu" - } - }, - "si" : { - "stringUnit" : { - "state" : "translated", - "value" : "පුද්. සබැඳිය පිටපත් කරන්න" + "value" : "Copiadu" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Kopírovať privátny odkaz" + "value" : "Skopírované" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj zasebno povezavo" + "value" : "Kopirano" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај приватну везу" + "value" : "Копирано" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiera privat länk" + "value" : "Kopierat" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Nakili kiungio binafsi" + "value" : "Iliyonakiliwa" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "நகலெடுக்கப்பட்டது" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Kişisel bağlantıyı kopyala" + "value" : "Kopyalandı" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "شەخسىي ئۇلىنىشنى كۆچۈرۈڭ" + "value" : "كۆچۈرۈلگەن" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Копіювати приватне посилання" + "value" : "Скопійовано" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sao chép liên kết riêng tư" + "value" : "Đã sao chép" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "复制私有链接" + "value" : "已复制" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "複製私人連結" + "value" : "已複製" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "複製私人連結" + "value" : "已複製" } } } }, - "Copy public link" : { - "comment" : "Copies the public link of an Anyone share.", + "Copy private link" : { + "comment" : "Copies the in-server link of the file for invited people.", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نسخ الرابط العمومي" + "value" : "نسخ الرابط الخاص" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar l'enllaz públicu" + "value" : "Copiar l'enllaz priváu" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Скапіяваць публічную спасылку" + "value" : "Капіяваць прыватную спасылку" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Копирай публичната връзка" + "value" : "Копирай скритата връзка" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Eilañ al liamm foran" + "value" : "Eilañ al liamm prevez" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Copia l'enllaç públic" + "value" : "Copia l'enllaç privat" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Zkopírovat veřejný odkaz" + "value" : "Zkopírovat soukromý odkaz" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copïo dolen gyhoeddus" + "value" : "Copïo dolen breifat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier offentligt link" + "value" : "Kopiér privat link" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Öffentlichen Link kopieren" + "value" : "Privaten Link kopieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αντιγραφή δημόσιου συνδέσμου" + "value" : "Αντιγραφή ιδιωτικού συνδέσμου" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Copy public link" + "value" : "Copy private link" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Copy public link" + "value" : "Copy private link" } }, - "es" : { + "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "Kopii privatan ligilon" } }, - "es_AR" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link publico" + "value" : "Copiar enlace privado" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "Copiar enlace privado" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar enlace público" + "value" : "Copiar enlace privado" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Kopeeri avalik link" + "value" : "Kopeeri privaatne link" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiatu esteka publikoa" + "value" : "Kopiatu esteka pribatua" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "کپی لینک عمومی" + "value" : "کپی پیوند خصوصی" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Kopioi julkinen linkki" + "value" : "Kopioi yksityinen linkki" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Copier le lien public" + "value" : "Copier le lien privé" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cóipeáil nasc poiblí" + "value" : "Cóipeáil nasc príobháideach" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar a ligazón pública" + "value" : "Copiar a ligazón privada" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "העתקת הקישור הציבורי" + "value" : "העתקת קישור פרטי" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj javnu poveznicu" + "value" : "Kopiraj privatnu poveznicu" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Nyilvános hivatkozás másolása" + "value" : "Személyes hivatkozás másolása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Salin tautan publik" + "value" : "Salin tautan privat" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Afrita opinberan tengil" + "value" : "Afrita einkatengil" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Copia collegamento pubblico" + "value" : "Copia collegamento privato" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "公開リンクをコピー" + "value" : "プライベートリンクをコピー" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Copy public link" + "value" : "Copy private link" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "공개 링크 복사" + "value" : "개인 링크 주소 복사" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ສຳເນົາລິ້ງສາທາລະນະ" + "value" : "Copy private link" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Kopijuoti viešąją nuorodą" - } - }, - "lv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ievietot publisko saiti starpliktuvē" + "value" : "Kopijuoti privačią nuorodą" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај јавен линк" + "value" : "Копирај приватен линк" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Нийтийн холбоос хуулах" + "value" : "Хувийн холбоос хуулах" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kopier offentlig lenke" + "value" : "Kopier privat lenke" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopieer openbare link" + "value" : "Privélink kopiëren" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar lo ligam public" + "value" : "Copiar lo ligam privat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiuj link publiczny" + "value" : "Kopiuj link prywatny" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar link público" + "value" : "Copiar link privado" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Copiar ligação publica" + "value" : "Copiar link privado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Copiză link-ul public" + "value" : "Copiază link privat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Скопировать общедоступную ссылку" + "value" : "Скопировать закрытую ссылку" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Còpia ligòngiu pùblicu" + "value" : "Còpia ligòngiu privadu" } }, - "sk_SK" : { + "si" : { "stringUnit" : { "state" : "translated", - "value" : "Kopírovať verejný odkaz" + "value" : "පුද්. සබැඳිය පිටපත් කරන්න" } }, - "sl" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiraj javno povezavo" + "value" : "Kopírovať privátny odkaz" + } + }, + "sl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopiraj zasebno povezavo" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Копирај јавни линк" + "value" : "Копирај приватну везу" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Kopiera offentlig länk" + "value" : "Kopiera privat länk" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Nakili kiungo cha umma" + "value" : "Nakili kiungio binafsi" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Herkese açık bağlantıyı kopyala" + "value" : "Kişisel bağlantıyı kopyala" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "ئاممىۋى ئۇلىنىشنى كۆچۈرۈڭ" + "value" : "شەخسىي ئۇلىنىشنى كۆچۈرۈڭ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Копіювати публічне посилання" - } - }, - "uz" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umumiy havoladan nusxa oling" + "value" : "Копіювати приватне посилання" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sao chép liên kết công khai" + "value" : "Sao chép liên kết riêng tư" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "复制公开链接" + "value" : "复制私有链接" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "複製公開連結" + "value" : "複製私人連結" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "複製公開連結" + "value" : "複製私人連結" } } } }, - "Could not create share, try again later" : { - "comment" : "Error message shown when a unified share cannot be created.", + "Copy public link" : { + "comment" : "Copies the public link of an Anyone share.", "localizations" : { - "da" : { + "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Delingen kunne ikke oprettes. Prøv igen senere." + "value" : "نسخ الرابط العمومي" } }, - "de" : { + "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Freigabe konnte nicht erstellt werden. Bitte später erneut versuchen." + "value" : "Copiar l'enllaz públicu" } }, - "en" : { + "be" : { "stringUnit" : { "state" : "translated", - "value" : "Could not create share, try again later" + "value" : "Скапіяваць публічную спасылку" } }, - "en_GB" : { + "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Could not create share, try again later" + "value" : "Копирай публичната връзка" } }, - "es" : { + "br" : { "stringUnit" : { "state" : "translated", - "value" : "No se ha podido compartir, inténtalo de nuevo más tarde" + "value" : "Eilañ al liamm foran" } }, - "et_EE" : { + "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Jagamise lisamine ei õnnestunud. Palun proovi hiljem uuesti." + "value" : "Copia l'enllaç públic" } }, - "fr" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de créer le partage, réessayez plus tard" + "value" : "Zkopírovat veřejný odkaz" } }, - "ga" : { + "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Níorbh fhéidir an comhroinnt a chruthú; déan iarracht arís níos déanaí" + "value" : "Copïo dolen gyhoeddus" } }, - "it" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile creare la condivisione; riprova più tardi." + "value" : "Kopier offentligt link" } }, - "pt_BR" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível criar o compartilhamento; tente novamente mais tarde" + "value" : "Öffentlichen Link kopieren" } }, - "ru" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось создать общий ресурс, повторите попытку позже" + "value" : "Αντιγραφή δημόσιου συνδέσμου" } }, - "sk_SK" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Nepodarilo sa vytvoriť zdieľanie, skúste to znova neskôr" + "value" : "Copy public link" } }, - "tr" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Paylaşım oluşturulamadı, bir süre sonra yeniden deneyin" + "value" : "Copy public link" } }, - "zh_HK" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "無法建立分享,請稍後再試" + "value" : "Copiar enlace público" } }, - "zh_TW" : { + "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "無法建立分享,請稍後再試" + "value" : "Copiar link publico" } - } - } - }, - "Couldn't load shares" : { - "comment" : "Full-screen error title when the share list fails to load.", - "localizations" : { - "da" : { + }, + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Delingerne kunne ikke indlæses" + "value" : "Copiar enlace público" } }, - "de" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Freigaben konnten nicht geladen werden" + "value" : "Copiar enlace público" } }, - "en" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Couldn't load shares" + "value" : "Kopeeri avalik link" } }, - "en_GB" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Couldn't load shares" + "value" : "Kopiatu esteka publikoa" } }, - "es" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Error al cargar" + "value" : "کپی لینک عمومی" } }, - "et_EE" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Jagamiste laadimine ei õnnestunud" + "value" : "Kopioi julkinen linkki" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Impossible de charger les partages" + "value" : "Copier le lien public" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Níorbh fhéidir scaireanna a luchtú" + "value" : "Cóipeáil nasc poiblí" } }, - "it" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Impossibile caricare le condivisioni" + "value" : "Copiar a ligazón pública" } }, - "pt_BR" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "Não foi possível carregar os compartilhamentos" + "value" : "העתקת הקישור הציבורי" } }, - "ru" : { + "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Не удалось загрузить список общих ресурсов" + "value" : "Kopiraj javnu poveznicu" } }, - "sk_SK" : { + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Nepodarilo sa načítať zdieľania" + "value" : "Nyilvános hivatkozás másolása" } }, - "tr" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "Paylaşımlar yüklenemedi" + "value" : "Salin tautan publik" } }, - "zh_HK" : { + "is" : { "stringUnit" : { "state" : "translated", - "value" : "無法載入分享" + "value" : "Afrita opinberan tengil" } }, - "zh_TW" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "無法載入分享" + "value" : "Copia collegamento pubblico" } - } - } - }, - "Custom link" : { - "comment" : "Heading of the editable public-link token row.", - "localizations" : { - "da" : { + }, + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Tilpasset link" + "value" : "公開リンクをコピー" } }, - "de" : { + "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzerdefinierter Link" + "value" : "Copy public link" } }, - "en" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Custom link" + "value" : "공개 링크 복사" } }, - "en_GB" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Custom link" + "value" : "ສຳເນົາລິ້ງສາທາລະນະ" } }, - "es" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Personalizar enlace" + "value" : "Kopijuoti viešąją nuorodą" } }, - "et_EE" : { + "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Kohendatud link" + "value" : "Ievietot publisko saiti starpliktuvē" } }, - "fr" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Lien personnalisé" + "value" : "Копирај јавен линк" } }, - "ga" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Nasc saincheaptha" + "value" : "Нийтийн холбоос хуулах" } }, - "it" : { + "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Link personalizzato" + "value" : "Kopier offentlig lenke" } }, - "pt_BR" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Link personalizado" + "value" : "Kopieer openbare link" } }, - "ru" : { + "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Пользовательская ссылка" + "value" : "Copiar lo ligam public" } }, - "sk_SK" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Vlastný odkaz" + "value" : "Kopiuj link publiczny" } }, - "tr" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Özel bağlantı" + "value" : "Copiar link público" } }, - "zh_HK" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "自訂連結" + "value" : "Copiar ligação publica" } }, - "zh_TW" : { + "ro" : { "stringUnit" : { "state" : "translated", - "value" : "自訂連結" + "value" : "Copiză link-ul public" } - } - } - }, - "Custom permissions" : { - "comment" : "Menu option that reveals per-permission toggles for custom permissions.", - "localizations" : { - "ar" : { + }, + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "أذونات مخصصة" + "value" : "Скопировать общедоступную ссылку" } }, - "ast" : { + "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Personlizar los permisos" + "value" : "Còpia ligòngiu pùblicu" } }, - "az" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Xüsusi icazələr" + "value" : "Kopírovať verejný odkaz" } }, - "bg_BG" : { + "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Персонализиране на права" + "value" : "Kopiraj javno povezavo" } }, - "ca" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalitzats" + "value" : "Копирај јавни линк" } }, - "cs_CZ" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Uživatelsky určená oprávnění" + "value" : "Kopiera offentlig länk" } }, - "da" : { + "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Brugerdefinerede rettigheder" + "value" : "Nakili kiungo cha umma" } }, - "de" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzerdefinierte Berechtigungen" + "value" : "Herkese açık bağlantıyı kopyala" } }, - "el" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Προσαρμοσμένα δικαιώματα" + "value" : "ئاممىۋى ئۇلىنىشنى كۆچۈرۈڭ" } }, - "en" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "Копіювати публічне посилання" } }, - "en_GB" : { + "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "Umumiy havoladan nusxa oling" } }, - "es" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Sao chép liên kết công khai" } }, - "es_EC" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "复制公开链接" } }, - "es_MX" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "複製公開連結" } }, - "et_EE" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Kohandatud õigused" + "value" : "複製公開連結" + } + } + } + }, + "Could not create share, try again later" : { + "comment" : "Error message shown when a unified share cannot be created.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delingen kunne ikke oprettes. Prøv igen senere." } }, - "eu" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Baimen pertsonalizatuak" + "value" : "Freigabe konnte nicht erstellt werden. Bitte später erneut versuchen." } }, - "fa" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "مجوزهای سفارشی" + "value" : "Could not create share, try again later" } }, - "fi_FI" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Mukautetut oikeudet" + "value" : "Could not create share, try again later" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se ha podido compartir, inténtalo de nuevo más tarde" + } + }, + "et_EE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jagamise lisamine ei õnnestunud. Palun proovi hiljem uuesti." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Autorisations personnalisées" + "value" : "Impossible de créer le partage, réessayez plus tard" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Ceadanna saincheaptha" + "value" : "Níorbh fhéidir an comhroinnt a chruthú; déan iarracht arís níos déanaí" } }, - "gl" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Permisos personalizados" + "value" : "Impossibile creare la condivisione; riprova più tardi." } }, - "hr" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Prilagođena dopuštenja" + "value" : "Não foi possível criar o compartilhamento; tente novamente mais tarde" } }, - "hu_HU" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Egyéni jogosultságok" + "value" : "Не удалось создать общий ресурс, повторите попытку позже" } }, - "id" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Izin kustom" + "value" : "Nepodarilo sa vytvoriť zdieľanie, skúste to znova neskôr" } }, - "is" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Sérsniðnar heimildir" + "value" : "Det gick inte att skapa delningen. Försök igen senare." } }, - "it" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Autorizzazioni personalizzate" + "value" : "Paylaşım oluşturulamadı, bir süre sonra yeniden deneyin" } }, - "ja_JP" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "カスタム権限" + "value" : "無法建立分享,請稍後再試" } }, - "ka" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Custom permissions" + "value" : "無法建立分享,請稍後再試" + } + } + } + }, + "Couldn't load shares" : { + "comment" : "Full-screen error title when the share list fails to load.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delingerne kunne ikke indlæses" } }, - "ko" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "사용자 지정 권한" + "value" : "Freigaben konnten nicht geladen werden" } }, - "lo" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "ສິດອະນຸຍາດແບບກຳນົດເອງ" + "value" : "Couldn't load shares" } }, - "lt_LT" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Tinkinti leidimai" + "value" : "Couldn't load shares" } }, - "mk" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Прилагодени дозволи" + "value" : "Error al cargar" } }, - "mn" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Тусгай эрхүүд" + "value" : "Jagamiste laadimine ei õnnestunud" } }, - "nb_NO" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Egendefinerte tillatelser" + "value" : "Impossible de charger les partages" } }, - "nl" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Aangepaste machtigingen" + "value" : "Níorbh fhéidir scaireanna a luchtú" } }, - "pl" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Uprawnienia niestandardowe" + "value" : "Impossibile caricare le condivisioni" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Permissões personalizadas" + "value" : "Não foi possível carregar os compartilhamentos" } }, - "pt_PT" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Permissões personalizadas" + "value" : "Не удалось загрузить список общих ресурсов" } }, - "ro" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Permisiuni particularizate" + "value" : "Nepodarilo sa načítať zdieľania" } }, - "ru" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Пользовательские права доступа" + "value" : "Det gick inte att läsa in delningar." } }, - "sc" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Permissos personalizados" + "value" : "Paylaşımlar yüklenemedi" } }, - "sk_SK" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Vlastné oprávnenia" + "value" : "無法載入分享" } }, - "sl" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Dovoljenja po meri" + "value" : "無法載入分享" + } + } + } + }, + "Custom link" : { + "comment" : "Heading of the editable public-link token row.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tilpasset link" } }, - "sr" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Произвољне дозволе" + "value" : "Benutzerdefinierter Link" } }, - "sv" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Anpassade behörigheter" + "value" : "Custom link" } }, - "sw" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Ruhusa maalum" + "value" : "Custom link" } }, - "tr" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Özel izinler" + "value" : "Personalizar enlace" } }, - "ug" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "ئىختىيارى ئىجازەت" + "value" : "Kohendatud link" } }, - "uk" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Спеціальні дозволи" + "value" : "Lien personnalisé" } }, - "vi" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Quyền tùy chỉnh" + "value" : "Nasc saincheaptha" } }, - "zh_CN" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "自定义权限" + "value" : "Link personalizzato" + } + }, + "pt_BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Link personalizado" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пользовательская ссылка" + } + }, + "sk_SK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vlastný odkaz" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anpassad länk" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Özel bağlantı" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "自訂權限" + "value" : "自訂連結" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "自訂權限" + "value" : "自訂連結" } } } @@ -4593,6 +4692,12 @@ "value" : "Odstrániť príjemcu" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort mottagare" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -4688,6 +4793,12 @@ "value" : "Odstrániť príjemcu?" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort mottagaren?" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -4927,6 +5038,12 @@ "value" : "Excluir compartilhamento" } }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Șterge partajarea" + } + }, "ru" : { "stringUnit" : { "state" : "translated", @@ -5088,6 +5205,12 @@ "value" : "Odstrániť zdieľanie?" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort delningen?" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -5110,7 +5233,6 @@ }, "Edit" : { "comment" : "Action that opens the unified share editor.", - "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5759,6 +5881,12 @@ "value" : "Nepodarilo sa aktualizovať zdieľanie." } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Det gick inte att uppdatera delningen." + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -5779,6 +5907,107 @@ } } }, + "Hide recipients" : { + "comment" : "Accessibility label for collapsing a share's recipient list.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjul modtagere" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Empfänger ausblenden" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hide recipients" + } + }, + "en_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hide recipients" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esconder destinatarios" + } + }, + "et_EE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Peida saajad" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Masquer les destinataires" + } + }, + "ga" : { + "stringUnit" : { + "state" : "translated", + "value" : "Folaigh na faighteoirí" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nascondi destinatari" + } + }, + "pt_BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar destinatários" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скрыть получателей" + } + }, + "sk_SK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skryť príjemcov" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dölj mottagare" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alıcıları gizle" + } + }, + "zh_HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隱藏接收者" + } + }, + "zh_TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "隱藏收件人" + } + } + } + }, "For people who already have access" : { "comment" : "Subtitle explaining that an internal link works only for people who already have access.", "localizations" : { @@ -5950,6 +6179,12 @@ "value" : "Para as pessoas que já têm acesso" } }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pentru persoanele care au deja acces" + } + }, "ru" : { "stringUnit" : { "state" : "translated", @@ -6024,101 +6259,6 @@ } } }, - "Hide recipients" : { - "comment" : "Accessibility label for collapsing a share's recipient list.", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skjul modtagere" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Empfänger ausblenden" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide recipients" - } - }, - "en_GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide recipients" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Esconder destinatarios" - } - }, - "et_EE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Peida saajad" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Masquer les destinataires" - } - }, - "ga" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folaigh na faighteoirí" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nascondi destinatari" - } - }, - "pt_BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ocultar destinatários" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Скрыть получателей" - } - }, - "sk_SK" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skryť príjemcov" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alıcıları gizle" - } - }, - "zh_HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "隱藏接收者" - } - }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "隱藏收件人" - } - } - } - }, "Internal link" : { "comment" : "Title for a link that works only for people who already have access.", "localizations" : { @@ -6547,6 +6687,12 @@ "value" : "Pozvaní ľudia" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inbjudna personer" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -6857,6 +7003,12 @@ "value" : "Prihlásenie zlyhalo" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inloggningen misslyckades" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -6952,6 +7104,12 @@ "value" : "Žiadni príjemcovia" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inga mottagare" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -7058,6 +7216,12 @@ "value" : "Zatiaľ nezdieľané" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inte delad ännu" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -7079,7 +7243,7 @@ } }, "OK" : { - "comment" : "Button label for error alert dismissal.", + "comment" : "Button that dismisses an error alert.", "localizations" : { "da" : { "stringUnit" : { @@ -7141,6 +7305,12 @@ "value" : "Добро" } }, + "mn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ок" + } + }, "pt_BR" : { "stringUnit" : { "state" : "translated", @@ -7159,6 +7329,12 @@ "value" : "OK" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -7715,6 +7891,12 @@ "value" : "Obnoviť odkaz" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdatera länken" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -7735,101 +7917,6 @@ } } }, - "Remove password" : { - "comment" : "Destructive action that removes the password from a share.", - "localizations" : { - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fjern adgangskode" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Passwort entfernen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove password" - } - }, - "en_GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove password" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eliminar contraseña" - } - }, - "et_EE" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eemalda salaõna" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Supprimer le mot de passe" - } - }, - "ga" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bain an focal faire" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rimuovi la password" - } - }, - "pt_BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remover senha" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Удалить пароль" - } - }, - "sk_SK" : { - "stringUnit" : { - "state" : "translated", - "value" : "Odstrániť heslo" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Parolayı kaldır" - } - }, - "zh_HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "移除密碼" - } - }, - "zh_TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "移除密碼" - } - } - } - }, "Retry" : { "comment" : "Button that retries loading the unified share list after an error.", "localizations" : { @@ -8321,2577 +8408,2689 @@ } } }, - "Save" : { - "comment" : "Button that confirms a text property change.", + "Scan QR Code" : { + "comment" : "Button label\nNavigation bar title", "localizations" : { - "af" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "an" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "ast" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "az" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "be" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "bg_BG" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "bn_BD" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "br" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "bs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desa" - } - }, - "cs_CZ" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Uložit" + "value" : "Scan QR-kode" } }, - "cy_GB" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "QR-Code scannen" } }, - "da" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Gem" + "value" : "Scan QR Code" } }, - "de" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Speichern" + "value" : "Scan QR Code" } }, - "el" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Αποθήκευση" + "value" : "Escanear código QR" } }, - "en" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Skaneeri QR-koodi" } }, - "en_GB" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Scanner le QR code" } }, - "eo" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Scanáil Cód QR" } }, - "es" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Guardar" + "value" : "Scansiona il QR Code" } }, - "es_419" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Скенирај QR код" } }, - "es_AR" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Escanear código QR" } }, - "es_CL" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Сканировать QR код" } }, - "es_CO" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Naskenovať QR kód" } }, - "es_CR" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Skanna QR-kod" } }, - "es_DO" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Kare kodu tara" } }, - "es_EC" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "掃描 QR 碼" } }, - "es_GT" : { + "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "掃描 QR code" } - }, - "es_HN" : { + } + } + }, + "Send" : { + "comment" : "Primary action button title in the unified share view.", + "localizations" : { + "af" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Stuur" } }, - "es_MX" : { + "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "أرسِل" } }, - "es_NI" : { + "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Unviar" } }, - "es_PA" : { + "az" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Göndər" } }, - "es_PE" : { + "be" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Адправіць" } }, - "es_PR" : { + "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Изпращане" } }, - "es_PY" : { + "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "পাঠাও" } }, - "es_SV" : { + "br" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Kas" } }, - "es_UY" : { + "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Pošalji" } }, - "et_EE" : { + "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Salvesta" + "value" : "Envia" } }, - "eu" : { + "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Odeslat" } }, - "fa" : { + "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "ذخیره" + "value" : "Anfon" } }, - "fi_FI" : { + "da" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Send" } }, - "fo" : { + "de" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Senden" } }, - "fr" : { + "el" : { "stringUnit" : { "state" : "translated", - "value" : "Enregistrer" + "value" : "Αποστολή" } }, - "ga" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Sábháil" + "value" : "Send" } }, - "gd" : { + "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Send" } }, - "gl" : { + "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Gardar" + "value" : "Sendi" } }, - "he" : { + "es" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "hi_IN" : { + "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "hr" : { + "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Spremi" + "value" : "Enviar" } }, - "hsb" : { + "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "hu_HU" : { + "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Mentés" + "value" : "Enviar" } }, - "hy" : { + "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "ia" : { + "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "id" : { + "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Simpan" + "value" : "Enviar" } }, - "ig" : { + "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "is" : { + "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Vista" + "value" : "Enviar" } }, - "it" : { + "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "ja_JP" : { + "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "保存する" + "value" : "Enviar" } }, - "ka" : { + "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "ka_GE" : { + "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "kab" : { + "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Sekles" + "value" : "Enviar" } }, - "km" : { + "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "kn" : { + "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "ko" : { + "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "la" : { + "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Saada" } }, - "lb" : { + "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Bidali" } }, - "lo" : { + "fa" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ارسال" } }, - "lt_LT" : { + "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Išsaugoti" + "value" : "Lähetä" } }, - "lv" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Envoyer" } }, - "mk" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Seol" } }, - "mn" : { + "gd" : { "stringUnit" : { "state" : "translated", - "value" : "–•–∞–¥–≥–∞–ª–∞—Ö" + "value" : "Cuir" } }, - "mr" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "ms_MY" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "שליחה" } }, - "my" : { + "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Pošaljite" } }, - "nb_NO" : { + "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Küldés" } }, - "ne" : { + "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Ուղարկել" } }, - "nl" : { + "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Opslaan" + "value" : "Inviar" } }, - "nn_NO" : { + "id" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Kirim" } }, - "oc" : { + "is" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Senda" } }, - "pl" : { + "it" : { "stringUnit" : { "state" : "translated", - "value" : "Zapisz" + "value" : "Invia" } }, - "ps" : { + "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "送る" } }, - "pt_BR" : { + "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Salvar" + "value" : "Send" } }, - "pt_PT" : { + "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "გაგზავნა" } }, - "ro" : { + "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Tuzna" } }, - "ru" : { + "km" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ផ្ញើ" } }, - "sc" : { + "kn" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ಕಳುಹಿಸಿ" } }, - "si" : { + "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "전송" } }, - "sk_SK" : { + "lb" : { "stringUnit" : { "state" : "translated", - "value" : "Uložiť" + "value" : "Schécken" } }, - "sl" : { + "lo" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "ສົ່ງ" } }, - "sq" : { + "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Siųsti" } }, - "sr" : { + "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Sūtīt" } }, - "sv" : { + "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Spara" + "value" : "Прати" } }, - "sw" : { + "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Hifadhi" + "value" : "–∏–ª–≥—ç—ç—Ö" } }, - "ta" : { + "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Hantar" } }, - "th_TH" : { + "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Send" } }, - "tk" : { + "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Verzenden" } }, - "tr" : { + "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Kaydet" + "value" : "Send" } }, - "ug" : { + "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "uk" : { + "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Wyślij" } }, - "ur_PK" : { + "ps" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "لېږل" } }, - "uz" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Saqlash" + "value" : "Enviar" } }, - "vi" : { + "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Enviar" } }, - "zh_CN" : { + "ro" : { "stringUnit" : { "state" : "translated", - "value" : "保存" + "value" : "Trimite" } }, - "zh_HK" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "儲存" + "value" : "Отправить" } }, - "zh_TW" : { + "si" : { "stringUnit" : { "state" : "translated", - "value" : "儲存" + "value" : "යවන්න" } }, - "zu_ZA" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Save" + "value" : "Odoslať" } - } - } - }, - "Scan QR Code" : { - "comment" : "Button label\nNavigation bar title", - "localizations" : { - "da" : { + }, + "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR-kode" + "value" : "Pošlji" } }, - "de" : { + "sq" : { "stringUnit" : { "state" : "translated", - "value" : "QR-Code scannen" + "value" : "Dërgo" } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR Code" + "value" : "Пошаљи" } }, - "en_GB" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Scan QR Code" + "value" : "Skicka" } }, - "es" : { + "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Escanear código QR" + "value" : "Tuma" } }, - "et_EE" : { + "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "Skaneeri QR-koodi" + "value" : "ส่ง" } }, - "fr" : { + "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Scanner le QR code" + "value" : "Iber" } }, - "ga" : { + "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Scanáil Cód QR" + "value" : "Gönder" } }, - "it" : { + "ug" : { "stringUnit" : { "state" : "translated", - "value" : "Scansiona il QR Code" + "value" : "ئەۋەتىڭ" } }, - "mk" : { + "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Скенирај QR код" + "value" : "Надіслати" } }, - "pt_BR" : { + "ur_PK" : { "stringUnit" : { "state" : "translated", - "value" : "Escanear código QR" + "value" : "بھجیں" } }, - "ru" : { + "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Сканировать QR код" + "value" : "Yuborish" } }, - "sk_SK" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Naskenovať QR kód" + "value" : "Gửi" } }, - "tr" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Kare kodu tara" + "value" : "发送" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "掃描 QR 碼" + "value" : "傳送" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "掃描 QR code" + "value" : "傳送" } } } }, - "Send" : { - "comment" : "Primary action button title in the unified share view.", + "Send email" : { + "comment" : "Legacy action for notifying unified-share recipients by email; currently not displayed.", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Stuur" + "value" : "Stuur e-pos" + } + }, + "an" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ninviar correu electronico" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أرسِل" + "value" : "إرسال بريد" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Unviar" + "value" : "Unviar un corréu electrónicu" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Göndər" + "value" : "Email yolla" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Адправіць" + "value" : "Адправіць электронны ліст" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Изпращане" + "value" : "Изпращане на имейл" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "পাঠাও" + "value" : "ইমেইল পাঠান " } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Kas" + "value" : "Kas postel" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji" + "value" : "Pošalji e-poštu" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Envia" + "value" : "Enviar correu" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Odeslat" + "value" : "Odeslat e-mail" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Anfon" + "value" : "Anfon e-bost" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Send e-mail" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Senden" + "value" : "E-Mail senden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αποστολή" + "value" : "Αποστολή email" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Send email" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Send email" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Sendi" + "value" : "Sendi retpoŝtmesaĝon" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar correo electrónico" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Saada" + "value" : "Saada e-kiri" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Bidali" + "value" : "Bidali mezu elektronikoa" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "ارسال" + "value" : "ارسال ایمیل" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Lähetä" + "value" : "Lähetä sähköposti" } }, - "fr" : { + "fo" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer" + "value" : "Send t-post" } }, - "ga" : { + "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Seol" + "value" : "Envoyer un e-mail" } }, - "gd" : { + "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cuir" + "value" : "Seol ríomhphost" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar o correo" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שליחה" + "value" : "שליחת דואר אלקטרוני" + } + }, + "hi_IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ईमेल भेजें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pošaljite" + "value" : "Pošalji poruku e-pošte" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Küldés" + "value" : "E-mail küldése" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Ուղարկել" + "value" : "Ուղարկել փոստ" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Inviar" + "value" : "Inviar message de e-posta" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kirim" + "value" : "Kirim email" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Senda" + "value" : "Senda tölvupóst" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia" + "value" : "Invia email" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "送る" + "value" : "メールを送信" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "ელ.ფოსტის გაგზავნა" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "გაგზავნა" + "value" : "ელ-წერილის გაგზავნა" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Tuzna" + "value" : "Azen imayl" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "ផ្ញើ" + "value" : "ផ្ញើ​អ៊ីមែល" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "ಕಳುಹಿಸಿ" + "value" : "ಇ-ಅಂಚೆಯನ್ನು ಕಳುಹಿಸಿ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "전송" - } - }, - "lb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schécken" + "value" : "이메일 보내기" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ສົ່ງ" + "value" : "ສົ່ງອີເມວ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Siųsti" + "value" : "Siųsti elektroninį laišką" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Sūtīt" + "value" : "Nosūtīt e-pasta ziņojumu" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Прати" + "value" : "Испрати е-пошта" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "–∏–ª–≥—ç—ç—Ö" + "value" : "Мэйл илгээх" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Hantar" + "value" : "Menghantar E-mel" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Send e-post" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verzenden" + "value" : "E-mail verzenden" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Send e-post" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar email" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij" - } - }, - "ps" : { - "stringUnit" : { - "state" : "translated", - "value" : "لېږل" + "value" : "Wyślij e-mail" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar e-mail" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Enviar mensagem" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Trimite" + "value" : "Trimite email" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить" + "value" : "Отправить письмо" } }, - "si" : { + "sc" : { "stringUnit" : { "state" : "translated", - "value" : "යවන්න" + "value" : "Imbia messàgiu de posta eletrònica" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odoslať" + "value" : "Odoslať email" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Pošlji" + "value" : "Pošlji elektronsko sporočilo" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Dërgo" + "value" : "Dërgo email" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Пошаљи" + "value" : "Пошаљи е-пошту" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Skicka" + "value" : "Skicka e-post" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Tuma" + "value" : "Tuma barua pepe" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "ส่ง" + "value" : "ส่งอีเมล" } }, "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Iber" + "value" : "E-poçta iber" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Gönder" + "value" : "E-posta gönder" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "ئەۋەتىڭ" + "value" : "ئېلېكترونلۇق خەت ئەۋەتىڭ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Надіслати" + "value" : "Запрошення" } }, - "ur_PK" : { + "uz" : { "stringUnit" : { "state" : "translated", - "value" : "بھجیں" + "value" : "Xabar yuborish" } }, - "uz" : { + "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Yuborish" + "value" : "Gửi email" } }, - "vi" : { + "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "Gửi" + "value" : "发送电子邮件" } }, - "zh_CN" : { + "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "发送" + "value" : "傳送電子郵件" + } + }, + "zh_TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送電子郵件" + } + } + } + }, + "Server Address" : { + "comment" : "Label for text field.", + "localizations" : { + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveradresse" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveradresse" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server Address" + } + }, + "en_GB" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server Address" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dirección del servidor" + } + }, + "et_EE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveri aadress" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adresse du serveur" + } + }, + "ga" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seoladh an Fhreastalaí" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indirizzo del server" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Адреса на сервер" + } + }, + "pt_BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endereço do servidor" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Адрес сервера" + } + }, + "sk_SK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adresa servera" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveradress" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sunucu adresi" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "傳送" + "value" : "伺服器位址" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "傳送" + "value" : "伺服器位址" } } } }, - "Send email" : { - "comment" : "Legacy action for notifying unified-share recipients by email; currently not displayed.", - "extractionState" : "stale", + "Settings" : { + "comment" : "Settings section title in the unified share view.", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Stuur e-pos" + "value" : "Instellings" } }, "an" : { "stringUnit" : { "state" : "translated", - "value" : "Ninviar correu electronico" + "value" : "Configuración" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إرسال بريد" + "value" : "الإعدادات" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Unviar un corréu electrónicu" + "value" : "Configuración" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Email yolla" + "value" : "Quraşdırmalar" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Адправіць электронны ліст" + "value" : "Налады" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Изпращане на имейл" + "value" : "Настройки" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "ইমেইল পাঠান " + "value" : "সেটিংস" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Kas postel" + "value" : "Arventennoù" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji e-poštu" + "value" : "Podešavanje" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correu" + "value" : "Paràmetres" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Odeslat e-mail" + "value" : "Settings" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Anfon e-bost" + "value" : "Gosodiadau" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-mail" + "value" : "Indstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "E-Mail senden" + "value" : "Einstellungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αποστολή email" + "value" : "Ρυθμίσεις" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Send email" + "value" : "Settings" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Send email" + "value" : "Settings" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Sendi retpoŝtmesaĝon" + "value" : "Agordoj" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Ajustes" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Ajustes" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuración" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Ajustes" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Ajustes" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Ajustes" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar correo electrónico" + "value" : "Configuraciones " } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Saada e-kiri" + "value" : "Seadistused" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Bidali mezu elektronikoa" + "value" : "Ezarpenak" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "ارسال ایمیل" + "value" : "تنظیمات" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Lähetä sähköposti" - } - }, - "fo" : { - "stringUnit" : { - "state" : "translated", - "value" : "Send t-post" + "value" : "Asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer un e-mail" + "value" : "Paramètres" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Seol ríomhphost" + "value" : "Socruithe" } }, - "gl" : { + "gd" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar o correo" + "value" : "Roghainnean" } }, - "he" : { + "gl" : { "stringUnit" : { "state" : "translated", - "value" : "שליחת דואר אלקטרוני" + "value" : "Axustes" } }, - "hi_IN" : { + "he" : { "stringUnit" : { "state" : "translated", - "value" : "ईमेल भेजें" + "value" : "הגדרות" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji poruku e-pošte" + "value" : "Postavke" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "E-mail küldése" + "value" : "Beállítások" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Ուղարկել փոստ" + "value" : "Կարգավորումներ" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Inviar message de e-posta" + "value" : "Configurationes" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kirim email" + "value" : "Pengaturan" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Senda tölvupóst" + "value" : "Stillingar" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia email" + "value" : "Impostazioni" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "メールを送信" + "value" : "設定" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "ელ.ფოსტის გაგზავნა" + "value" : "ოფციები" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "ელ-წერილის გაგზავნა" + "value" : "პარამეტრები" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Azen imayl" + "value" : "Iɣewwaren" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "ផ្ញើ​អ៊ីមែល" + "value" : "ការកំណត់" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "ಇ-ಅಂಚೆಯನ್ನು ಕಳುಹಿಸಿ" + "value" : "ಆಯ್ಕೆ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "이메일 보내기" + "value" : "설정" + } + }, + "lb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Astellungen" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ສົ່ງອີເມວ" + "value" : "ການຕັ້ງຄ່າ" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Siųsti elektroninį laišką" + "value" : "Nustatymai" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Nosūtīt e-pasta ziņojumu" + "value" : "Iestatījumi" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Испрати е-пошта" + "value" : "Параметри" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "Мэйл илгээх" + "value" : "–¢–æ—Ö–∏—Ä–≥–æ–æ" + } + }, + "mr" : { + "stringUnit" : { + "state" : "translated", + "value" : "सेटिंग" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Menghantar E-mel" + "value" : "Tetapan" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-post" + "value" : "Innstillinger" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "सेटिङ्हरू" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "E-mail verzenden" + "value" : "Instellingen" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Send e-post" + "value" : "Innstillingar" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar email" + "value" : "Paramètres" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij e-mail" + "value" : "Ustawienia" + } + }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "سمونې" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar e-mail" + "value" : "Configurações" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar mensagem" + "value" : "Definições" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Trimite email" + "value" : "Setări" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить письмо" + "value" : "Настройки" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Imbia messàgiu de posta eletrònica" + "value" : "Cunfiguratzione" + } + }, + "si" : { + "stringUnit" : { + "state" : "translated", + "value" : "සැකසුම්" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odoslať email" + "value" : "Nastavenia" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Pošlji elektronsko sporočilo" + "value" : "Nastavitve" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Dërgo email" + "value" : "Rregullimet" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Пошаљи е-пошту" + "value" : "Поставке" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Skicka e-post" + "value" : "Inställningar" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Tuma barua pepe" + "value" : "Mipangilio" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "அமைப்புகள்" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "ส่งอีเมล" + "value" : "การตั้งค่า" } }, "tk" : { "stringUnit" : { "state" : "translated", - "value" : "E-poçta iber" + "value" : "Sazlamalar" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "E-posta gönder" + "value" : "Ayarlar" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "ئېلېكترونلۇق خەت ئەۋەتىڭ" + "value" : "تەڭشەكلەر" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Запрошення" + "value" : "Налаштування" + } + }, + "ur_PK" : { + "stringUnit" : { + "state" : "translated", + "value" : "سیٹینگز" } }, "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Xabar yuborish" + "value" : "Sozlamalar" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Gửi email" + "value" : "Thiết lập" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "发送电子邮件" + "value" : "设置" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "傳送電子郵件" + "value" : "設定" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "傳送電子郵件" + "value" : "設定" } } } }, - "Server Address" : { - "comment" : "Label for text field.", + "Remove password" : { + "comment" : "Destructive action that removes the password from a share.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Serveradresse" + "value" : "Fjern adgangskode" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Serveradresse" + "value" : "Passwort entfernen" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Server Address" + "value" : "Remove password" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Server Address" + "value" : "Remove password" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Dirección del servidor" + "value" : "Eliminar contraseña" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Serveri aadress" + "value" : "Eemalda salaõna" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Adresse du serveur" + "value" : "Supprimer le mot de passe" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Seoladh an Fhreastalaí" + "value" : "Bain an focal faire" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Indirizzo del server" + "value" : "Rimuovi la password" } }, - "mk" : { + "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Адреса на сервер" + "value" : "Remover senha" } }, - "pt_BR" : { + "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Endereço do servidor" + "value" : "Удалить пароль" } }, - "ru" : { + "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес сервера" + "value" : "Odstrániť heslo" } }, - "sk_SK" : { + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Adresa servera" + "value" : "Ta bort lösenord" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sunucu adresi" + "value" : "Parolayı kaldır" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "伺服器位址" + "value" : "移除密碼" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "伺服器位址" + "value" : "移除密碼" } } } }, - "Settings" : { - "comment" : "Settings section title in the unified share view.", + "Save" : { + "comment" : "Button that confirms a text property change.", "localizations" : { "af" : { "stringUnit" : { "state" : "translated", - "value" : "Instellings" + "value" : "Save" } }, "an" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Save" } }, "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الإعدادات" + "value" : "Save" } }, "ast" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Save" } }, "az" : { "stringUnit" : { "state" : "translated", - "value" : "Quraşdırmalar" + "value" : "Save" } }, "be" : { "stringUnit" : { "state" : "translated", - "value" : "Налады" + "value" : "Save" } }, "bg_BG" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Save" } }, "bn_BD" : { "stringUnit" : { "state" : "translated", - "value" : "সেটিংস" + "value" : "Save" } }, "br" : { "stringUnit" : { "state" : "translated", - "value" : "Arventennoù" + "value" : "Save" } }, "bs" : { "stringUnit" : { "state" : "translated", - "value" : "Podešavanje" + "value" : "Save" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Paràmetres" + "value" : "Desa" } }, "cs_CZ" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Uložit" } }, "cy_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Gosodiadau" + "value" : "Save" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indstillinger" + "value" : "Gem" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" + "value" : "Speichern" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ρυθμίσεις" + "value" : "Αποθήκευση" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Save" } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Save" } }, "eo" : { "stringUnit" : { "state" : "translated", - "value" : "Agordoj" + "value" : "Save" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Guardar" } }, "es_419" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Save" } }, "es_AR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Save" } }, "es_CL" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_CO" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_CR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_DO" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_EC" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Save" } }, "es_GT" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_HN" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_MX" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Save" } }, "es_NI" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_PA" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_PE" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Save" } }, "es_PR" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_PY" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_SV" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "es_UY" : { "stringUnit" : { "state" : "translated", - "value" : "Configuraciones " + "value" : "Save" } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Seadistused" + "value" : "Salvesta" } }, "eu" : { "stringUnit" : { "state" : "translated", - "value" : "Ezarpenak" + "value" : "Save" } }, "fa" : { "stringUnit" : { "state" : "translated", - "value" : "تنظیمات" + "value" : "ذخیره" } }, "fi_FI" : { "stringUnit" : { "state" : "translated", - "value" : "Asetukset" + "value" : "Save" + } + }, + "fo" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Enregistrer" } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Socruithe" + "value" : "Sábháil" } }, "gd" : { "stringUnit" : { "state" : "translated", - "value" : "Roghainnean" + "value" : "Save" } }, "gl" : { "stringUnit" : { "state" : "translated", - "value" : "Axustes" + "value" : "Gardar" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הגדרות" + "value" : "Save" + } + }, + "hi_IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke" + "value" : "Spremi" + } + }, + "hsb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "hu_HU" : { "stringUnit" : { "state" : "translated", - "value" : "Beállítások" + "value" : "Mentés" } }, "hy" : { "stringUnit" : { "state" : "translated", - "value" : "Կարգավորումներ" + "value" : "Save" } }, "ia" : { "stringUnit" : { "state" : "translated", - "value" : "Configurationes" + "value" : "Save" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengaturan" + "value" : "Simpan" + } + }, + "ig" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "is" : { "stringUnit" : { "state" : "translated", - "value" : "Stillingar" + "value" : "Vista" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni" + "value" : "Save" } }, "ja_JP" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "保存する" } }, "ka" : { "stringUnit" : { "state" : "translated", - "value" : "ოფციები" + "value" : "Save" } }, "ka_GE" : { "stringUnit" : { "state" : "translated", - "value" : "პარამეტრები" + "value" : "Save" } }, "kab" : { "stringUnit" : { "state" : "translated", - "value" : "Iɣewwaren" + "value" : "Sekles" } }, "km" : { "stringUnit" : { "state" : "translated", - "value" : "ការកំណត់" + "value" : "Save" } }, "kn" : { "stringUnit" : { "state" : "translated", - "value" : "ಆಯ್ಕೆ" + "value" : "Save" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "설정" + "value" : "Save" + } + }, + "la" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "lb" : { "stringUnit" : { "state" : "translated", - "value" : "Astellungen" + "value" : "Save" } }, "lo" : { "stringUnit" : { "state" : "translated", - "value" : "ການຕັ້ງຄ່າ" + "value" : "Save" } }, "lt_LT" : { "stringUnit" : { "state" : "translated", - "value" : "Nustatymai" + "value" : "Išsaugoti" } }, "lv" : { "stringUnit" : { "state" : "translated", - "value" : "Iestatījumi" + "value" : "Save" } }, "mk" : { "stringUnit" : { "state" : "translated", - "value" : "Параметри" + "value" : "Save" } }, "mn" : { "stringUnit" : { "state" : "translated", - "value" : "–¢–æ—Ö–∏—Ä–≥–æ–æ" + "value" : "–•–∞–¥–≥–∞–ª–∞—Ö" } }, "mr" : { "stringUnit" : { "state" : "translated", - "value" : "सेटिंग" + "value" : "Save" } }, "ms_MY" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapan" + "value" : "Save" + } + }, + "my" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } }, "nb_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillinger" + "value" : "Save" } }, "ne" : { "stringUnit" : { "state" : "translated", - "value" : "सेटिङ्हरू" + "value" : "Save" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen" + "value" : "Opslaan" } }, "nn_NO" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillingar" + "value" : "Save" } }, "oc" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres" + "value" : "Save" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia" + "value" : "Zapisz" } }, "ps" : { "stringUnit" : { "state" : "translated", - "value" : "سمونې" + "value" : "Save" } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurações" + "value" : "Salvar" } }, "pt_PT" : { "stringUnit" : { "state" : "translated", - "value" : "Definições" + "value" : "Save" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Setări" + "value" : "Save" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Save" } }, "sc" : { "stringUnit" : { "state" : "translated", - "value" : "Cunfiguratzione" + "value" : "Save" } }, "si" : { "stringUnit" : { "state" : "translated", - "value" : "සැකසුම්" + "value" : "Save" } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavenia" + "value" : "Uložiť" } }, "sl" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavitve" + "value" : "Save" } }, "sq" : { "stringUnit" : { "state" : "translated", - "value" : "Rregullimet" + "value" : "Save" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Поставке" + "value" : "Save" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Inställningar" + "value" : "Spara" } }, "sw" : { "stringUnit" : { "state" : "translated", - "value" : "Mipangilio" + "value" : "Hifadhi" } }, "ta" : { "stringUnit" : { "state" : "translated", - "value" : "அமைப்புகள்" + "value" : "Save" } }, "th_TH" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่า" + "value" : "Save" } }, "tk" : { "stringUnit" : { "state" : "translated", - "value" : "Sazlamalar" + "value" : "Save" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Ayarlar" + "value" : "Kaydet" } }, "ug" : { "stringUnit" : { "state" : "translated", - "value" : "تەڭشەكلەر" + "value" : "Save" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Налаштування" + "value" : "Save" } }, "ur_PK" : { "stringUnit" : { "state" : "translated", - "value" : "سیٹینگز" + "value" : "Save" } }, "uz" : { "stringUnit" : { "state" : "translated", - "value" : "Sozlamalar" + "value" : "Saqlash" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Thiết lập" + "value" : "Save" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "设置" + "value" : "保存" } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "儲存" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "儲存" + } + }, + "zu_ZA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" } } } @@ -10971,6 +11170,12 @@ "value" : "Zdieľať \"%@\"" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela ”%@”" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -11341,10 +11546,16 @@ "value" : "Zdieľať priečinok" } }, - "sr" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дели фолдер" + } + }, + "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Дели фолдер" + "value" : "Dela mapp" } }, "tr" : { @@ -11369,7 +11580,6 @@ }, "Share link" : { "comment" : "Row title for a public link share.", - "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -11795,7 +12005,6 @@ }, "Share link (%lld)" : { "comment" : "Row title for the nth public link share of a file.", - "extractionState" : "stale", "localizations" : { "da" : { "stringUnit" : { @@ -11857,6 +12066,12 @@ "value" : "Link de compartilhamento (%lld)" } }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Link de partajare (%lld)" + } + }, "ru" : { "stringUnit" : { "state" : "translated", @@ -11869,6 +12084,12 @@ "value" : "Odkaz na zdieľanie (%lld)" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delningslänk (%lld)" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -11964,6 +12185,12 @@ "value" : "Zobraziť príjemcov" } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa mottagare" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -11984,572 +12211,608 @@ } } }, - "The address of your Nextcloud web interface when you open it in your browser." : { - "comment" : "Label below the server address field in the login view.", + "This change will replace the individual permission settings of all recipients in this share." : { + "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Adressen på din Nextcloud-webgrænseflade, når du åbner den i din browser." + "value" : "Denne ændring erstatter de individuelle rettigheder for alle modtagere af denne deling." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die Adresse Ihrer Nextcloud-Weboberfläche, wenn Sie diese in Ihrem Browser öffnen." + "value" : "Diese Änderung ersetzt die individuellen Berechtigungseinstellungen aller Empfänger dieser Freigabe." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The address of your Nextcloud web interface when you open it in your browser." + "value" : "This change will replace the individual permission settings of all recipients in this share." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The address of your Nextcloud web interface when you open it in your browser." + "value" : "This change will replace the individual permission settings of all recipients in this share." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La dirección de la interfaz web Nextcloud cuando la abras en tu navegador." + "value" : "El cambio reemplazará los ajustes de permisos individuales de todos los destinatarios de este recurso compartido." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sinu Nextcloudi kasutajaliidese veebiaadress, kui sa avad ta veebibrauseris." + "value" : "Selle muudatusega asendad kõikide jagamise saajate isiklikud õigused üldiste õigustega." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'adresse de votre interface web Nextcloud quand vous l'ouvrez dans votre navigateur." + "value" : "Cette modification remplacera les paramètres des autorisations individuelles de tous les destinataires de ce partage." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Seoladh do chomhéadain gréasáin Nextcloud nuair a osclaíonn tú é i do bhrabhsálaí." + "value" : "Cuirfidh an t-athrú seo ionad na socruithe ceada aonair do gach faighteoir sa scair seo." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'indirizzo dell'interfaccia web di Nextcloud quando la apri nel browser." + "value" : "Questa modifica sostituirà le impostazioni di autorizzazione individuali di tutti i destinatari di questa condivisione." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O endereço do site Nextcloud ao abri-lo no navegador." + "value" : "Essa alteração substituirá as configurações de permissão individuais de todos os destinatários deste compartilhamento." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Адрес Вашего веб-интерфейса Nextcloud, когда вы открываете его в Вашем браузере." + "value" : "Это изменение заменит индивидуальные настройки прав доступа всех получателей в этом общем ресурсе." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Adresa vášho webového rozhrania Nextcloud, keď ho otvoríte v prehliadači." + "value" : "Táto zmena nahradí individuálne nastavenia oprávnení všetkých príjemcov v tomto zdieľaní." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändringen ersätter de individuella behörighetsinställningarna för alla mottagare av den här delningen." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Nextcloud internet arayüzü için tarayıcınızda açacağınız adres." + "value" : "Bu değişiklik, bu paylaşımdaki tüm alıcıların bireysel izin ayarlarının yerini alacak." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "你在瀏覽器中開啟 Nextcloud 網頁介面時使用的位址。" + "value" : "此變更會取代此分享中所有接收者的個別權限設定。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "您在瀏覽器中開啟 Nextcloud 網頁介面的位址。" + "value" : "此變更會取代此分享中所有收件人的個別權限設定。" } } } }, - "The entered server address is invalid." : { - "comment" : "This is an error message.", + "The address of your Nextcloud web interface when you open it in your browser." : { + "comment" : "Label below the server address field in the login view.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Den indtastede serveradresse er ugyldig." + "value" : "Adressen på din Nextcloud-webgrænseflade, når du åbner den i din browser." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die eingegebene Serveradresse ist ungültig." + "value" : "Die Adresse Ihrer Nextcloud-Weboberfläche, wenn Sie diese in Ihrem Browser öffnen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The entered server address is invalid." + "value" : "The address of your Nextcloud web interface when you open it in your browser." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The entered server address is invalid." + "value" : "The address of your Nextcloud web interface when you open it in your browser." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La dirección del servidor ingresada no es válida." + "value" : "La dirección de la interfaz web Nextcloud cuando la abras en tu navegador." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sisestatud serveriaadress on vigane." + "value" : "Sinu Nextcloudi kasutajaliidese veebiaadress, kui sa avad ta veebibrauseris." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'adresse du serveur saisie n'est pas valide." + "value" : "L'adresse de votre interface web Nextcloud quand vous l'ouvrez dans votre navigateur." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Tá an seoladh freastalaí a iontráladh neamhbhailí." + "value" : "Seoladh do chomhéadain gréasáin Nextcloud nuair a osclaíonn tú é i do bhrabhsálaí." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'indirizzo del server non è valido." + "value" : "L'indirizzo dell'interfaccia web di Nextcloud quando la apri nel browser." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O endereço do servidor inserido é inválido." + "value" : "O endereço do site Nextcloud ao abri-lo no navegador." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введённый адрес сервера недействителен." + "value" : "Адрес вашего веб-интерфейса Nextcloud, когда вы открываете его в вашем браузере." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Zadaná adresa servera je neplatná." + "value" : "Adresa vášho webového rozhrania Nextcloud, keď ho otvoríte v prehliadači." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adressen till Nextclouds webbgränssnitt när du öppnar det i webbläsaren." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yazılan sunucu adresi geçersiz." + "value" : "Nextcloud internet arayüzü için tarayıcınızda açacağınız adres." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "輸入的伺服器位址無效。" + "value" : "你在瀏覽器中開啟 Nextcloud 網頁介面時使用的位址。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "輸入的伺服器位址無效。" + "value" : "您在瀏覽器中開啟 Nextcloud 網頁介面的位址。" } } } }, - "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." : { - "comment" : "Help text below the editable token of a custom public link.", + "The entered server address is invalid." : { + "comment" : "This is an error message.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Linket kan ændres, så det er let at huske, men vælg ikke noget, der er let at gætte." + "value" : "Den indtastede serveradresse er ugyldig." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Der Link kann so geändert werden, dass er leicht zu merken ist, aber wählen Sie keinen Link, der leicht zu erraten ist." + "value" : "Die eingegebene Serveradresse ist ungültig." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." + "value" : "The entered server address is invalid." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." + "value" : "The entered server address is invalid." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El enlace puede ser cambiado para ser fácil de recordar, pero no lo cambies a algo que sea fácil de adivinar." + "value" : "La dirección del servidor ingresada no es válida." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Seda linki saad muuta kergesti meeldejäetavaks, aga palun ära muuda selliseks, mis oleks liiga lihtne." + "value" : "Sisestatud serveriaadress on vigane." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Le lien peut être changé pour être plus simple à retenir, mais ne le définissez pas sur quelque chose de facile à deviner." + "value" : "L'adresse du serveur saisie n'est pas valide." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Is féidir an nasc a athrú ionas go mbeidh sé éasca a mheabhrú, ach ná socraigh é ar rud éigin atá éasca a thomhas." + "value" : "Tá an seoladh freastalaí a iontráladh neamhbhailí." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Il link può essere modificato per renderlo facile da ricordare, ma non impostarlo su qualcosa di facile da indovinare." + "value" : "L'indirizzo del server non è valido." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "O link pode ser alterado para ficar fácil de lembrar, mas não o defina como algo que seja fácil de adivinhar." + "value" : "O endereço do servidor inserido é inválido." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ссылку можно изменить так, чтобы её было легко запомнить, но не стоит выбирать название, которое легко угадать." + "value" : "Введённый адрес сервера недействителен." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Odkaz je možné zmeniť tak, aby sa dal ľahko zapamätať, ale nenastavujte ho na niečo, čo sa dá ľahko uhádnuť." + "value" : "Zadaná adresa servera je neplatná." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveradressen du angav är ogiltig." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bağlantı hatırlanması kolay olacak şekilde değiştirilebilir. Ancak kolay bulunabilecek bir şeye ayarlamayın." + "value" : "Yazılan sunucu adresi geçersiz." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "你可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" + "value" : "輸入的伺服器位址無效。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "您可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" + "value" : "輸入的伺服器位址無效。" } } } }, - "This change will replace the individual permission settings of all recipients in this share." : { - "comment" : "Explanation shown before propagating top-level permission changes to every recipient.", + "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." : { + "comment" : "Help text below the editable token of a custom public link.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Denne ændring erstatter de individuelle rettigheder for alle modtagere af denne deling." + "value" : "Linket kan ændres, så det er let at huske, men vælg ikke noget, der er let at gætte." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Änderung ersetzt die individuellen Berechtigungseinstellungen aller Empfänger dieser Freigabe." + "value" : "Der Link kann so geändert werden, dass er leicht zu merken ist, aber wählen Sie keinen Link, der leicht zu erraten ist." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This change will replace the individual permission settings of all recipients in this share." + "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This change will replace the individual permission settings of all recipients in this share." + "value" : "The link can be changed to be easy to remember, but do not set it to something that is easy to guess." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El cambio reemplazará los ajustes de permisos individuales de todos los destinatarios de este recurso compartido." + "value" : "El enlace puede ser cambiado para ser fácil de recordar, pero no lo cambies a algo que sea fácil de adivinar." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Selle muudatusega asendad kõikide jagamise saajate isiklikud õigused üldiste õigustega." + "value" : "Seda linki saad muuta kergesti meeldejäetavaks, aga palun ära muuda selliseks, mis oleks liiga lihtne." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Cette modification remplacera les paramètres des autorisations individuelles de tous les destinataires de ce partage." + "value" : "Le lien peut être changé pour être plus simple à retenir, mais ne le définissez pas sur quelque chose de facile à deviner." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Cuirfidh an t-athrú seo ionad na socruithe ceada aonair do gach faighteoir sa scair seo." + "value" : "Is féidir an nasc a athrú ionas go mbeidh sé éasca a mheabhrú, ach ná socraigh é ar rud éigin atá éasca a thomhas." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questa modifica sostituirà le impostazioni di autorizzazione individuali di tutti i destinatari di questa condivisione." + "value" : "Il link può essere modificato per renderlo facile da ricordare, ma non impostarlo su qualcosa di facile da indovinare." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Essa alteração substituirá as configurações de permissão individuais de todos os destinatários deste compartilhamento." + "value" : "O link pode ser alterado para ficar fácil de lembrar, mas não o defina como algo que seja fácil de adivinhar." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Это изменение заменит индивидуальные настройки прав доступа всех получателей в этом общем ресурсе." + "value" : "Ссылку можно изменить так, чтобы её было легко запомнить, но не стоит выбирать название, которое легко угадать." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Táto zmena nahradí individuálne nastavenia oprávnení všetkých príjemcov v tomto zdieľaní." + "value" : "Odkaz je možné zmeniť tak, aby sa dal ľahko zapamätať, ale nenastavujte ho na niečo, čo sa dá ľahko uhádnuť." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kan ändra länken så att den blir lätt att komma ihåg, men välj inte något som är lätt att gissa." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu değişiklik, bu paylaşımdaki tüm alıcıların bireysel izin ayarlarının yerini alacak." + "value" : "Bağlantı hatırlanması kolay olacak şekilde değiştirilebilir. Ancak kolay bulunabilecek bir şeye ayarlamayın." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此變更會取代此分享中所有接收者的個別權限設定。" + "value" : "你可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此變更會取代此分享中所有收件人的個別權限設定。" + "value" : "您可以將連結改為容易記住的內容,但請勿設為容易猜到的內容。" } } } }, - "This recipient will be removed from the share." : { - "comment" : "Message of the recipient removal confirmation dialog.", + "This share is limited to \"%@\". You can only grant the same or fewer permissions." : { + "comment" : "Explains that recipient permissions cannot exceed the share-wide preset; the placeholder is the preset name.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modtageren fjernes fra delingen." + "value" : "Denne deling er begrænset til \"%1$@\". Du kan kun give de samme eller færre rettigheder." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dieser Empfänger wird aus der Freigabe entfernt." + "value" : "Diese Freigabe ist auf \"%1$@\" beschränkt. Sie können nur dieselben oder weniger Berechtigungen erteilen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This recipient will be removed from the share." + "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This recipient will be removed from the share." + "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Este destinatario será eliminado del recurso compartido." + "value" : "Este recurso compartido está limitado a %1$@\". Sólo puedes dar los mismos o menos permisos." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "See saaja eemaldatakse jagamisest." + "value" : "Sellel jagamisel kehtib piirang (%1$@) ja sa võid jagada vaid samade või vähemate õigustega." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ce destinataire sera retiré du partage." + "value" : "Ce partage est limité à \"%1$@\". Vous pouvez accorder, au maximum, ce nombre d'autorisations." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Bainfear an faighteoir seo den scair." + "value" : "Tá an comhroinnt seo teoranta do \"%1$@\". Ní féidir leat ach na ceadanna céanna nó níos lú ná iad a dheonú." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questo destinatario verrà rimosso dalla condivisione." + "value" : "Questa quota è limitata a \"%1$@\". È possibile concedere solo gli stessi permessi o un numero inferiore di permessi." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Esse destinatário será removido do compartilhamento." + "value" : "Essa compartilhamento está limitado a “%1$@”. Você só pode conceder as mesmas permissões ou menos do que essas." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Этот получатель будет удален из общего доступа." + "value" : "Этот общий ресурс ограничен правами \"%1$@\". Вы можете предоставить только те же или меньшие права доступа." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Tento príjemca bude odstránený zo zdieľania." + "value" : "Toto zdieľanie je obmedzené na \"%1$@\". Môžete udeliť len rovnaké alebo menšie oprávnenia." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den här delningen är begränsad till ”%1$@”. Du kan bara ge samma eller färre behörigheter." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu alıcı paylaşımdan kaldırılacak." + "value" : "Bu paylaşım \"%1$@\" ile sınırlı. Yalnızca aynı veya daha az izinler verebilirsiniz." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此接收者將會從分享中移除。" + "value" : "此分享的權限僅限於「%1$@」。你只能授予相同或更少的權限。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此收件人將會從分享中移除。" + "value" : "此分享的權限僅限於「%1$@」。您僅能授予相同或更少的權限。" } } } }, - "This share is limited to \"%@\". You can only grant the same or fewer permissions." : { - "comment" : "Explains that recipient permissions cannot exceed the share-wide preset; the placeholder is the preset name.", + "This recipient will be removed from the share." : { + "comment" : "Message of the recipient removal confirmation dialog.", "localizations" : { "da" : { "stringUnit" : { "state" : "translated", - "value" : "Denne deling er begrænset til \"%1$@\". Du kan kun give de samme eller færre rettigheder." + "value" : "Modtageren fjernes fra delingen." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Freigabe ist auf \"%1$@\" beschränkt. Sie können nur dieselben oder weniger Berechtigungen erteilen." + "value" : "Dieser Empfänger wird aus der Freigabe entfernt." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." + "value" : "This recipient will be removed from the share." } }, "en_GB" : { "stringUnit" : { "state" : "translated", - "value" : "This share is limited to \"%1$@\". You can only grant the same or fewer permissions." + "value" : "This recipient will be removed from the share." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Este recurso compartido está limitado a %1$@\". Sólo puedes dar los mismos o menos permisos." + "value" : "Este destinatario será eliminado del recurso compartido." } }, "et_EE" : { "stringUnit" : { "state" : "translated", - "value" : "Sellel jagamisel kehtib piirang (%1$@) ja sa võid jagada vaid samade või vähemate õigustega." + "value" : "See saaja eemaldatakse jagamisest." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Ce partage est limité à \"%1$@\". Vous pouvez accorder, au maximum, ce nombre d'autorisations." + "value" : "Ce destinataire sera retiré du partage." } }, "ga" : { "stringUnit" : { "state" : "translated", - "value" : "Tá an comhroinnt seo teoranta do \"%1$@\". Ní féidir leat ach na ceadanna céanna nó níos lú ná iad a dheonú." + "value" : "Bainfear an faighteoir seo den scair." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Questa quota è limitata a \"%1$@\". È possibile concedere solo gli stessi permessi o un numero inferiore di permessi." + "value" : "Questo destinatario verrà rimosso dalla condivisione." } }, "pt_BR" : { "stringUnit" : { "state" : "translated", - "value" : "Essa compartilhamento está limitado a “%1$@”. Você só pode conceder as mesmas permissões ou menos do que essas." + "value" : "Esse destinatário será removido do compartilhamento." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Этот общий ресурс ограничен правами \"%1$@\". Вы можете предоставить только те же или меньшие права доступа." + "value" : "Этот получатель будет удален из общего доступа." } }, "sk_SK" : { "stringUnit" : { "state" : "translated", - "value" : "Toto zdieľanie je obmedzené na \"%1$@\". Môžete udeliť len rovnaké alebo menšie oprávnenia." + "value" : "Tento príjemca bude odstránený zo zdieľania." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den här mottagaren tas bort från delningen." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bu paylaşım \"%1$@\" ile sınırlı. Yalnızca aynı veya daha az izinler verebilirsiniz." + "value" : "Bu alıcı paylaşımdan kaldırılacak." } }, "zh_HK" : { "stringUnit" : { "state" : "translated", - "value" : "此分享的權限僅限於「%1$@」。你只能授予相同或更少的權限。" + "value" : "此接收者將會從分享中移除。" } }, "zh_TW" : { "stringUnit" : { "state" : "translated", - "value" : "此分享的權限僅限於「%1$@」。您僅能授予相同或更少的權限。" + "value" : "此收件人將會從分享中移除。" } } } @@ -12629,6 +12892,12 @@ "value" : "Toto zdieľanie bude natrvalo odstránené." } }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Den här delningen tas bort permanent." + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -12651,4 +12920,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift index 18648ddc..d9cdf020 100644 --- a/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorLocalizedGenericMessageTests.swift @@ -7,9 +7,7 @@ import Foundation import Testing @testable import NextcloudKit -/// /// Tests for the message ``NKError/localizedGenericMessage`` shows to users. -/// struct NKErrorLocalizedGenericMessageTests { @Test(arguments: NKError.Kind.allCases) func kindMessage(kind: NKError.Kind) { From e05c94b0517323ebddde74c9f69eb7b0b7ea51f3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 20 Oct 2026 15:48:23 +0200 Subject: [PATCH 8/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/NKError+Kind.swift | 2 +- Tests/NextcloudKitUnitTests/NKErrorKindTests.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/NextcloudKit/NKError+Kind.swift b/Sources/NextcloudKit/NKError+Kind.swift index c0c2f353..534e6104 100644 --- a/Sources/NextcloudKit/NKError+Kind.swift +++ b/Sources/NextcloudKit/NKError+Kind.swift @@ -97,7 +97,7 @@ extension NKError.Kind { case NSURLErrorTimedOut: self = .timeout - case NSURLErrorCannotFindHost, NSURLErrorCannotConnectToHost, NSURLErrorNetworkConnectionLost, NSURLErrorDNSLookupFailed: + case NSURLErrorBadURL, NSURLErrorUnsupportedURL, NSURLErrorCannotFindHost, NSURLErrorCannotConnectToHost, NSURLErrorNetworkConnectionLost, NSURLErrorDNSLookupFailed: self = .serverUnreachable case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasUnknownRoot, diff --git a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift index d2794da1..fc30f9e1 100644 --- a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift @@ -15,6 +15,7 @@ struct NKErrorKindTests { (NSURLErrorNotConnectedToInternet, .offline), (NSURLErrorTimedOut, .timeout), (NSURLErrorCannotFindHost, .serverUnreachable), + (NSURLErrorBadURL, .serverUnreachable), (NSURLErrorServerCertificateUntrusted, .secureConnection), (NSURLErrorUserAuthenticationRequired, .unauthorized), (NSURLErrorCannotParseResponse, .invalidResponse), From 0244193d4a60535d097ef3040bf8f13fae5b07a4 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 20 Oct 2026 16:42:55 +0200 Subject: [PATCH 9/9] WIP Signed-off-by: Milen Pivchev --- Sources/NextcloudKit/NKError.swift | 23 ++++++++++--------- .../NextcloudKit+UnifiedSharing.swift | 22 +++++------------- .../NKErrorKindTests.swift | 11 +++++++++ 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index 6ec99bbd..1649ba8f 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -30,6 +30,7 @@ extension OCSPath { static var ocsMetaCode: Self { ["ocs", "meta", "statuscode"] } static var ocsMetaMsg: Self { ["ocs", "meta", "message"] } static var ocsDataMsg: Self { ["ocs", "data", "message"] } + static var ocsData: Self { ["ocs", "data"] } static var ocsXMLMsg: Self { ["d:error", "s:message"] } static var davException: Self { ["d:error", "s:exception"] } } @@ -67,11 +68,15 @@ public struct NKError: Error, Equatable, Sendable { public static let success = NKError(errorCode: 0, errorDescription: "") - /// The share API's error message, which Nextcloud writes for users. Other server texts are technical. + /// The server's message where Nextcloud writes it for users: the share API and unified sharing's property updates. + /// Other server texts are technical. public var userFriendlyServerMessage: String? { - guard requestPath?.contains("/apps/files_sharing/api/v1/shares") == true else { return nil } + guard let requestPath else { return nil } - return serverMessage + let isShareAPI = requestPath.contains("/apps/files_sharing/api/v1/shares") + let isUnifiedShareProperty = requestPath.contains("/apps/sharing/api/v1/share/") && requestPath.hasSuffix("/property") + + return isShareAPI || isUnifiedShareProperty ? serverMessage : nil } /// Returns the English description of a known error code, for logs. @@ -265,13 +270,9 @@ public struct NKError: Error, Equatable, Sendable { let statuscode = rootJson[.ocsMetaCode].int ?? fallbackStatusCode ?? NSURLErrorCannotDecodeContentData errorCode = 200..<300 ~= statuscode ? 0 : statuscode - if let dataMsg = rootJson[.ocsDataMsg].string { - errorDescription = dataMsg - } else if let metaMsg = rootJson[.ocsMetaMsg].string { - errorDescription = metaMsg - } else { - errorDescription = NKError.getErrorDescription(for: statuscode) ?? NKError.httpFallbackDescription(for: statuscode) - } + let message = NKError.firstNonEmpty(rootJson[.ocsDataMsg].string, rootJson[.ocsData].string, rootJson[.ocsMetaMsg].string) + + errorDescription = message ?? NKError.getErrorDescription(for: statuscode) ?? NKError.httpFallbackDescription(for: statuscode) self.responseData = responseData self.error = NSError( @@ -280,7 +281,7 @@ public struct NKError: Error, Equatable, Sendable { userInfo: [NSLocalizedDescriptionKey: self.errorDescription] ) self.kind = errorCode == 0 ? nil : Kind(code: statuscode) - self.serverMessage = NKError.firstNonEmpty(rootJson[.ocsDataMsg].string, rootJson[.ocsMetaMsg].string) + self.serverMessage = message } /// Creates an `NKError` from an HTTP status code. diff --git a/Sources/NextcloudKit/NextcloudKit+UnifiedSharing.swift b/Sources/NextcloudKit/NextcloudKit+UnifiedSharing.swift index cd4e03b0..4d963928 100644 --- a/Sources/NextcloudKit/NextcloudKit+UnifiedSharing.swift +++ b/Sources/NextcloudKit/NextcloudKit+UnifiedSharing.swift @@ -97,7 +97,7 @@ public extension NextcloudKit { switch response.result { case .failure(let error): - return (account, nil, response, unifiedShareError(from: response, error: error)) + return (account, nil, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success(let data): do { let wrap = try JSONDecoder().decode(NKOCSWrapper<[NKUnifiedShareRecipient]>.self, from: data) @@ -135,7 +135,7 @@ public extension NextcloudKit { switch response.result { case .failure(let error): - return (account, nil, response, unifiedShareError(from: response, error: error)) + return (account, nil, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success(let data): do { let wrap = try JSONDecoder().decode(NKOCSWrapper.self, from: data) @@ -176,7 +176,7 @@ public extension NextcloudKit { switch response.result { case .failure(let error): - return (account, nil, response, unifiedShareError(from: response, error: error)) + return (account, nil, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success(let data): do { let wrap = try JSONDecoder().decode(NKOCSWrapper.self, from: data) @@ -294,7 +294,7 @@ public extension NextcloudKit { switch response.result { case .failure(let error): - return (account, response, unifiedShareError(from: response, error: error)) + return (account, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success: return (account, response, .success) } @@ -591,7 +591,7 @@ public extension NextcloudKit { ) -> (account: String, share: NKUnifiedShare?, responseData: AFDataResponse?, error: NKError) { switch response.result { case .failure(let error): - return (account, nil, response, unifiedShareError(from: response, error: error)) + return (account, nil, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success(let data): do { let wrap = try JSONDecoder().decode(NKOCSWrapper.self, from: data) @@ -605,16 +605,6 @@ public extension NextcloudKit { } } - /// Unified-sharing error bodies carry the message as the whole `ocs.data` string. - private func unifiedShareError(from response: AFDataResponse, error: AFError) -> NKError { - if let data = response.data, - let wrap = try? JSONDecoder().decode(NKOCSWrapper.self, from: data), - !wrap.ocs.data.isEmpty { - return NKError(errorCode: wrap.ocs.meta.statuscode, errorDescription: wrap.ocs.data, responseData: data) - } - - return NKError(error: error, afResponse: response, responseData: response.data) - } /// Decode an OCS response containing an array of `Share`. private func decodeUnifiedShareList(response: AFDataResponse, @@ -622,7 +612,7 @@ public extension NextcloudKit { ) -> (account: String, shares: [NKUnifiedShare]?, responseData: AFDataResponse?, error: NKError) { switch response.result { case .failure(let error): - return (account, nil, response, unifiedShareError(from: response, error: error)) + return (account, nil, response, NKError(error: error, afResponse: response, responseData: response.data)) case .success(let data): do { let wrap = try JSONDecoder().decode(NKOCSWrapper<[NKUnifiedShare]>.self, from: data) diff --git a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift index fc30f9e1..91e341b6 100644 --- a/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift +++ b/Tests/NextcloudKitUnitTests/NKErrorKindTests.swift @@ -49,6 +49,7 @@ struct NKErrorKindTests { private static let davURL = URL(string: "https://cloud.example.com/remote.php/dav/files/alice/Documents")! private static let shareURL = URL(string: "https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares/42")! + private static let unifiedSharePropertyURL = URL(string: "https://cloud.example.com/ocs/v2.php/apps/sharing/api/v1/share/abc/property")! private func davErrorBody(exception: String, message: String) -> Data { Data(""" @@ -131,8 +132,18 @@ struct NKErrorKindTests { #expect(error.userFriendlyServerMessage == "Public upload disabled by the administrator") } + @Test("Passes on unified sharing's property validation messages") + func unifiedSharePropertyMessage() { + let body = Data(#"{"ocs":{"meta":{"status":"failure","statuscode":400,"message":""},"data":"Password needs to be at least 10 characters long"}}"#.utf8) + let error = responseError(url: Self.unifiedSharePropertyURL, statusCode: 400, method: "PUT", body: body) + + #expect(error.userFriendlyServerMessage == "Password needs to be at least 10 characters long") + #expect(error.errorDescription == "Password needs to be at least 10 characters long") + } + @Test("Keeps the messages of other OCS APIs in the logs only", arguments: [ "https://cloud.example.com/ocs/v2.php/apps/sharing/api/v1/share/abc", + "https://cloud.example.com/ocs/v2.php/apps/sharing/api/v1/share/abc/state", "https://cloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/sharees" ]) func otherOCSMessages(url: String) throws {