Skip to content

Commit

Permalink
Version 3.34.4
Browse files Browse the repository at this point in the history
  • Loading branch information
webim committed Apr 30, 2021
1 parent 091d810 commit b4588fe
Show file tree
Hide file tree
Showing 85 changed files with 2,270 additions and 1,507 deletions.
Binary file added Documentation/Images/ConnectionImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Documentation/Images/Logo.png
Binary file not shown.
Binary file added Documentation/Images/logo_webim.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 19 additions & 3 deletions Documentation/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ Returns `self` with location name set.
Method is mandatory to create [WebimSession](#webim-session) object.

<h3 id ="set-prechat">Instance method set(prechat:)</h3>
Attention: this method can't be used as is. It requires that client server to support this mechanism!
Sets prechat fields for the session.
`prechat` parameter – `String`-typed prechat fields in JSON format.
Returns `self` with location name set.
Expand Down Expand Up @@ -483,6 +484,7 @@ Method is not mandatory to create [WebimSession](#webim-session) object.

<h3 id ="set-multivisitor-section">Instance method set(multivisitorSection:)</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Sets necesarity to receive remote notifications by different visitors on one device. Without multivisitor section only last visitor can receive remote notifications.
`multivisitorSection` parameter – suffix for device ID. Each visitor has device ID. This parameter can split one device to some virtual devices.
Returns `self` with the functionality activation setting.
Expand Down Expand Up @@ -573,7 +575,7 @@ Error that is thrown when trying to create session object with invalid remote no

When client provides custom visitor authorization mechanism, it can be realised by providing custom authorization token which is used instead of visitor fields.
When provided authorization token is generated (or passed to session by client app), `update(providedAuthorizationToken:)` method is called. This method call indicates that client app must send provided authorisation token to its server which is responsible to send it to Webim service.
This mechanism can't be used as is. It requires that client server to support this mecahnism.
This mechanism can't be used as is. It requires that client server to support this mechanism.

<h3 id ="update-provided-authorization-token">update(providedAuthorizationToken:) method</h3>

Expand Down Expand Up @@ -679,6 +681,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="rate-operator-with-id-note-by-rating-rating">rateOperatorWith(id:note:byRating:completionHandler:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Rates an operator.
To get an ID of the current operator call [getCurrentOperator()](#get-current-operator).
`id` parameter – String-typed ID of the operator to be rated. Optional: if `nil` is passed, current chat operator will be rated.
Expand All @@ -689,6 +692,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="respond-sentry-call">respondSentryCall(id:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Respond sentry call.
`id` parameter – String-typed ID of redirect to sentry message.
Can throw errors of [AccessError](#access-error) type.
Expand All @@ -707,6 +711,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="start-chat-custom-fields">startChat(customFields:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Changes [ChatState](#chat-state) to [queue](#queue). Starts chat with custom fields.
Method call is not mandatory. Starts chat with custom fields.
`customFields` paramater – String-typed custom fields in JSON format.
Expand All @@ -730,13 +735,15 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="start-chat-first-question-custom-fields">startChat(firstQuestion:customFields:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Starts chat with custom fields and sends first message simultaneously.
Changes [ChatState](#chat-state) to [queue](#queue).
If account settings provide automatic complimentary message it won't be sent before any "startChat" method or first sent message.
Can throw errors of [AccessError](#access-error) type.

<h3 id ="start-chat-department-key-custom-fields">startChat(departmentKey:customFields:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Starts chat with particular department and custom fields. Department is identified by `departmentKey` parameter (see [getKey()](#get-key) of [Department](#department) protocol)
Changes [ChatState](#chat-state) to [queue](#queue).
In most cases method call is not mandatory, send message or send file methods start chat automatically. But it is mandatory when [VisitSessionState](#visit-session-state) is in [departmentSelection state](#department-selection).
Expand All @@ -745,6 +752,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="start-chat-department-key-first-question-custom-fields">startChat(departmentKey:firstQuestion:customFields:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Starts chat with particular department and customFields and sends first message simultaneously. Department is identified by `departmentKey` parameter (see [getKey()](#get-key) of [Department](#department) protocol)
Changes [ChatState](#chat-state) to [queue](#queue).
In most cases method call is not mandatory, send message or send file methods start chat automatically. But it is mandatory when [VisitSessionState](#visit-session-state) is in [departmentSelection state](#department-selection).
Expand All @@ -764,6 +772,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="send-message-data">send(message:data:completionHandler:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Sends a text message.
When calling this method, if there is an active [MessageTracker](#message-tracker) object. [added(message newMessage:,after previousMessage:) method](#added-message-new-message-after-previous-message)) with a message [sending case](#sending) in the status is also called.
`message` parameter – `String`-typed message text.
Expand All @@ -774,6 +783,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="send-message-is-hint-question">send(message:isHintQuestion:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Sends a text message.
When calling this method, if there is an active [MessageTracker](#message-tracker) object. [added(message newMessage:,after previousMessage:) method](#added-message-new-message-after-previous-message)) with a message [sending case](#sending) in the status is also called.
`message` parameter – `String`-typed message text.
Expand Down Expand Up @@ -810,7 +820,8 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="update-widget-status">updateWidgetStatus(data:) method</h3>

Update widget status. The change is displayed by the operator..
Attention: this method can't be used as is. It requires that client server to support this mechanism!
Update widget status. The change is displayed by the operator.
`data` parameter – JSON string with new widget status.
Can throw errors of [AccessError](#access-error) type.

Expand All @@ -834,7 +845,7 @@ When calling this method, if there is an active [MessageTracker](#message-tracke
Returns true if message can be edited.
Can throw errors of [AccessError](#access-error) type.

<h3 id ="delete-message">edit(message:text:completionHandler:) method</h3>
<h3 id ="delete-message">delete(message:completionHandler:) method</h3>

Deletes a text message.
Before calling this method recommended to find out the possibility of editing the message using [canBeEdited() method](#can-be-edited).
Expand All @@ -861,6 +872,7 @@ Can throw errors of [AccessError](#access-error) type.

<h3 id ="set-prechat-fields">set(prechatFields:) method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Sends prechat fields to server.
Can throw errors of [AccessError](#access-error) type.

Expand Down Expand Up @@ -915,6 +927,7 @@ Sets [UnreadByVisitorTimestampChangeListener](#unread-by-visitor-timestamp-chang

<h2 id ="data-message-completion-handler">DataMessageCompletionHandler protocol</h2>

Attention: this mechanism can't be used as is. It requires that client server to support this mechanism!
Protocol which methods are called after [send(message:data:completionHandler:)](#send-message-data) method is finished. Must be adopted.

<h3 id ="on-success-message-id-data-message-completion-handler">onSuccess(messageID:) method</h3>
Expand Down Expand Up @@ -1271,6 +1284,7 @@ First status is not recieved yet or status is not supported by this version of t

<h2 id ="data-message-error">DataMessageError enum</h2>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Error types that could be passed in [onFailure(messageID:error:) method](#on-failure-message-id-error-data-message-completion-handler).

<h3 id ="unknown-data-message-error">unknown case</h3>
Expand Down Expand Up @@ -1954,10 +1968,12 @@ Returns parameters of this remote notification of array of `String` type.

<h3 id ="get-location">getLocation() method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Returns location of this remote notification of array of type `String` type.

<h3 id ="get-unread-by-visitor-messages-count">getUnreadByVisitorMessagesCount() method</h3>

Attention: this method can't be used as is. It requires that client server to support this mechanism!
Returns unread by visitor messages count of this remote notification of array of `Int` type.

[Go to table of contents](#table-of-contents)
Expand Down
6 changes: 6 additions & 0 deletions Example/Localizer/AdditionalKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// additional keys that should be localized, but not present in code or UI files

// MARK: - Remote notifications
// "P.OA".localized
// "P.OF".localized
// "P.OM".localized
185 changes: 185 additions & 0 deletions Example/Localizer/ProjectParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//
// ProjectParser.swift
// localizer2
//
// Created by EVGENII Loshchenko on 22.03.2021.
//

import Cocoa
typealias Value = String
typealias CodeKey = String
typealias FileName = String
typealias Locale = String

typealias XibFileData = [FileName: [CodeKey: Value]]
typealias Translations = [Locale: [CodeKey: Value]]
typealias XibTranslations = [Locale: [CodeKey: Value]]

// swiftlint:disable force_unwrapping
class ProjectParser: NSObject {

static func run(_ projectFolderPath: String) {

// get all code keys
var codeKeys = [CodeKey: [FileName]]()
getAllCodeKeys(projectFolderPath: projectFolderPath, codeKeys: &codeKeys)

// get all xib keys
var xibKeysMap = XibFileData()
getAllXibKeys(projectFolderPath: projectFolderPath, codeKeys: &codeKeys, xibKeysMap: &xibKeysMap)

let xibTranslations = loadAllXibTranslations(projectFolderPath: projectFolderPath, xibKeysMap: xibKeysMap)

// read and update translations
let translations = updateAllTranslations(projectFolderPath: projectFolderPath, codeKeys: &codeKeys, xibTranslations: xibTranslations)

// update xib localizations
updateXibTranslations(projectFolderPath: projectFolderPath, xibKeysMap: xibKeysMap, translations: translations, xibTranslations: xibTranslations)

}

static func loadAllXibTranslations(projectFolderPath: String, xibKeysMap: XibFileData) -> XibTranslations {

var xibTranslations = XibTranslations()

let stringsFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: ".strings")
for file in stringsFiles {

if !file.hasSuffix("Localizable.strings") && !file.hasSuffix("InfoPlist.strings") {
let locale = file.getLocale()

var locationDictionary = xibTranslations[locale] ?? [String: String]()
let fileName = file.lastPathComponent.nameWithoutExtension()

let text = ShellWrapper.readUTF8File(file)
let localizedValues: [String: String] = StringsFileParser.getKeysFromStringsFile(text: text)

let xibKeyDixtionary = xibKeysMap[fileName] ?? [String: String]()
for key in localizedValues.keys {
locationDictionary[key] = localizedValues[key]

if let xibKey = xibKeyDixtionary[key] {
locationDictionary[xibKey] = localizedValues[key]
}
}

xibTranslations[locale] = locationDictionary
}
}

return xibTranslations
}

static func updateXibTranslations(projectFolderPath: String, xibKeysMap: XibFileData, translations: Translations, xibTranslations: XibTranslations ) {
let stringsFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: ".strings")
for filePath in stringsFiles {
if !filePath.hasSuffix("Localizable.strings") && !filePath.hasSuffix("InfoPlist.strings"){

let locale = filePath.getLocale()
let filename = filePath.nameWithoutExtension().lastPathComponent
let localeTranslations = translations[locale] ?? [:]
let localeXibTranslations: [CodeKey: Value] = xibTranslations[locale] ?? [CodeKey: Value]()
let xibKeys = xibKeysMap[filename] ?? [:]

var fileString = ""
for key in xibKeys.keys.sorted() {
var translatedValue = localeTranslations[xibKeys[key]!] ?? ""
if translatedValue.isEmpty {
translatedValue = localeXibTranslations[key] ?? ""
}
if translatedValue.isEmpty {
translatedValue = xibKeys[key] ?? ""
}
fileString += "\"\(key)\" = \"\(translatedValue)\";\n"
}
fileString.writeToFile(filePath)
}

}
}

static func updateAllTranslations(projectFolderPath: String, codeKeys: inout [CodeKey: [FileName]], xibTranslations: XibTranslations) -> Translations {
var baseTranslation = [String: String]()
var translations = Translations()
let stringsFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: "Localizable.strings")
for filePath in stringsFiles.reversed() {

print(filePath)
let locale = filePath.getLocale()

let text = ShellWrapper.readUTF8File(filePath)
let localizedValues: [String: String] = StringsFileParser.getKeysFromStringsFile(text: text)
translations[locale] = localizedValues

let localeXibTranslations: [CodeKey: Value] = xibTranslations[locale] ?? [CodeKey: Value]()

var fileString = ""
for key in codeKeys.keys.sorted() {
var translation = localizedValues[key] ?? ""

if translation.isEmpty {
translation = localeXibTranslations[key] ?? ""
}
if translation.isEmpty {
translation = key
}

let comment = codeKeys[key]?.sorted().joined(separator: ",") ?? ""
fileString += "//" + comment + "\n"
fileString += "\"\(key)\" = \"\(translation)\";\n"

if locale == "Base.lproj" {
baseTranslation[key] = translation
}
}
fileString.writeToFile(filePath)
}
return translations
}

static func getAllCodeKeys(projectFolderPath: String, codeKeys: inout [CodeKey: [FileName]]) {

let codeFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: ".swift")
for filePath in codeFiles {

let fileName = filePath.lastPathComponent

let keys = StringsFileParser.getCodeKeysFromFile(filePath: filePath)

for key in keys {
addKey(key, fromFile: fileName, to: &codeKeys)
}
}
}

static func addKey(_ key: String, fromFile fileName: String, to codeKeys: inout [CodeKey: [FileName]]) {
if codeKeys[key] == nil {
codeKeys[key] = [String]()
}
if !codeKeys[key]!.contains(fileName) {
codeKeys[key]?.append(fileName)
}
}

static func getAllXibKeys(projectFolderPath: String, codeKeys: inout [CodeKey: [FileName]], xibKeysMap: inout XibFileData) {
let xibFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: ".xib")
let storyboardFiles = StringsFileParser.filesList(inFolder: projectFolderPath, withSuffix: ".storyboard")
let UIFiles = xibFiles + storyboardFiles

for file in UIFiles {

let xibKeys = StringsFileParser.getXibKeysFromFile(filePath: file)

for key in xibKeys.keys {

addKey(xibKeys[key]!, fromFile: file.lastPathComponent, to: &codeKeys)

}

xibKeysMap[file.lastPathComponent.nameWithoutExtension()] = xibKeys
}

}

}
// swiftlint:enable force_unwrapping
48 changes: 48 additions & 0 deletions Example/Localizer/ShellWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// ShellWrapper.swift
// localizer2
//
// Created by EVGENII Loshchenko on 22.03.2021.
//

import Cocoa
import Foundation

class ShellWrapper: NSObject {
static let tempStringsFileName = "temp_strings_file.strings"

static func shell(_ args: [String]) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
}

static func readUTF8File(_ filePath: String) -> String {
var text = ""
do {
text = try String(contentsOfFile: filePath, encoding: .utf8)
} catch { print("readUTF8File error \(filePath)") }
return text
}

static func readUTF16File(_ filePath: String) -> String {
var text = ""
do {
text = try String(contentsOfFile: filePath, encoding: .utf16)
} catch { print("readUTF16File error \(filePath)") }
return text
}

static func generateStringsFileForXib(_ filePath: String) -> String {

var text = ""
shell(["ibtool", filePath, "--generate-strings-file", tempStringsFileName])
text = readUTF16File(tempStringsFileName)
shell(["rm", tempStringsFileName])

return text
}

}
Loading

0 comments on commit b4588fe

Please sign in to comment.