SwiftUI Text and LocalizedStringKey with parameter - ios

I am trying to add a SwiftUI localized Text with parameter. However in the whole app we use a wrapper around localization keys so we have something like this:
static let helloWorldText = NSLocalizedString("helloWorldText", comment: "")
with usage
label.text = LocalizationWrapper.helloWorldText
or
Text(LocalizationWrapper.helloWorldText)
and it works fine.
Then when it comes to adding a localized text with parameter it doesn't seems to work.
So I have a key "helloWorld %#"
and I have a static let helloWorldWithParameter = NSLocalizedString("helloWorld %#", comment: "")
now i tried to do this:
Text("\(LocalizationWrapper.helloWorldWithParameter) \(name)")
Text(LocalizedStringKey(LocalizationWrapper.helloWorldWithParameter + " " + name))
Text(LocalizationWrapper.helloWorldWithParameter + " " + name)
none of them works, however Text("helloWorld \(name)") works just fine.
Then i tried to remove NSLocalizedString leaving only LocalizationWrapper.helloWorldWithParameter as a clean string but it allso didn't do a thing
How can I make this work? I seen THIS but it is kind of dirty.

Use this extension
extension String {
public func localized(with arguments: [CVarArg]) -> String {
return String(format: NSLocalizedString(self, comment: ""), locale: nil, arguments: arguments)
}
}
Usage :
Text(LocalizationWrapper.helloWorldWithParameter.localized(with: [name]))
Also, This approach is correct
static func helloWithParameter(parameter: String) -> LocalizedStringKey {
return "helloWorld \(parameter)"
}

Related

How to trim several textfield in swift at once?

I have several textfield in a page. Currently i'm trimming every textfield one by one like code below.
public struct CustomerAddressDetail: Encodable {
public var fullname: String?
public var identifierNumber: String?
}
var customerInfoDetail = CustomerInfoDetail()
customerInfoDetail.fullname = itemsInput[0][0].value?.trimmingCharacters(in: .whitespaces) ?? ""
customerInfoDetail.identifierNumber = itemsInput[0][1].value?.trimmingCharacters(in: .whitespaces) ?? ""
I try to trim multiple value at once. Because it looks like a bad way to trim it one by one
Is it really like that ? Or is there any better way to trim multiple item ?
It looks bad to you because you have duplicate code. A nicer way to it is using iteration or map like this:
let stringlist: [String] = ["a ", "b ", "c "]
let trimmedlist = stringlist.map { s in
return s.trimmingCharacters(in: .whitespaces)
}

Delete non alphabetic characters at the starting of String

I want to replace all the non-alphanumeric characters (including white spaces) of a string up to the first alphabet
"\r\n A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
I used
let string = txtView.text
let newString = string.replacingOccurrences(of: "\n", with: "")
but it replaces all the "\n" of string. how could we replace the starting characters only?
I am able to remove leading white spaces using regular expression. This is the code :
extension String {
func trimLeadingWhitespaces() -> String {
return self.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression)
}
}
let string = " Some string abc"
let trimmed = string.trimLeadingWhitespaces()
print(trimmed)
There is a function drop that removes characters while a condition is filled.
Here are two variants of using it
let newString = string.drop { $0.isWhitespace || $0.isPunctuation }
let newString = string.drop { !($0.isLetter || $0.isNumber) }
You can use trimmingCharacters method of string class. It will remove all the leading and trailing whitespaces and \n from string.
let str = "\r\n A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
let result = str.trimmingCharacters(in: .whitespacesAndNewlines)
print(result)
Only leading spaces:
extension String {
func removingLeadingSpaces() -> String {
guard let index = firstIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: .whitespaces) }) else {
return self
}
return String(self[index...])
}
}
you can use two func from extension (thx #amer):
extension String {
func replacingFirstOccurrence(of string: String, with replacement: String) -> String {
guard let range = self.range(of: string) else { return self }
return replacingCharacters(in: range, with: replacement)
}
func removingLeadingSpaces() -> String {
guard let index = firstIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: .whitespaces) }) else {
return self
}
return String(self[index...])
}
}
and use it:
let myString = "\r\n A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
let charactersRemoved = myString.replacingFirstOccurrence(of: "\r\n", with: "")
let whiteSpacesRemoved = charactersRemoved.removingLeadingSpaces()
print(whiteSpacesRemoved) // "A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
[ |\t|\n|\r] matchs for space, newline, tabulation or carriage return
{2,} "2 times or more"
each match is replaced with a newline
let str = "\r\n A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
let newString = str.replacingOccurrences(
of: "[ |\t|\n|\r]{2,}",
with: "\n",
options: .regularExpression
)
print(newString)
// A Simple PDF File
// This is a small demonstration .pdf file -
// just for use in the Virtual Mechanics tutorials. More text. And more
// text.
As per your problem statement you need to delete non-alphabetic char which includes spaces, line,'#','$','%','^', etc. There is much non-alphabetic char. Instead of checking non-alphabetic char , you chan validate alphabetic char alone. Please check the following solution to your problem.
extension String{
func findFirstAlphabetic() -> String.Index?{
for index in self.indices{
if String(self[index]).isAlphanumeric == true{
return index
}
}
return nil
}
var isAlphanumeric: Bool {
return !isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil
}
func alphabetic_Leading_SubString() -> String?{
if let startIndex = self.findFirstAlphabetic(){
let newSubString = self[startIndex..<self.endIndex]
return String(newSubString)
}
return nil
}
}
usage:-
print("Output" + string.alphabetic_Leading_SubString())
Input :-
let string = "\r\n# # $ % & ^ * () -+ ######## A Simple PDF File \r\n This is a small demonstration .pdf file - \r\n just for use in the Virtual Mechanics tutorials. More text. And more \r\n text."
Output:-

Swift: how to censor/filter text entered for swear words, etc?

I just want to see whether there is an established way to do this, or how one would go about this.
I have a text field that essentially acts as a form in my iOs app where a user can post something. I can't have users posting swear words/inappropriate crap, so I want to filter out and display an error if the string they enter contains one of these words.
How do other apps in Swift do this? Do they just search through the string to see if it contains the word (obviously not within other words, but standing alone) or is there another method?
How can I accurately filter out the swear words from my user post in Swift?
Construct a list of words you consider to be swear words, and simply check the user entered string whether any of these words are contained within the string.
Swift 3:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.contains($1.lowercased()) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(text: userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(text: userEnteredText2, swearWords: listOfSwearWords)) // false
Swift 2.2:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.containsString($1.lowercaseString) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(userEnteredText2, swearWords: listOfSwearWords)) // false
I created a class that enables you to feed a string in and remove profanity. Here's a link to the repo.
Here's the code:
class ProfanityFilter: NSObject {
static let sharedInstance = ProfanityFilter()
private override init() {}
// Customize as needed
private let dirtyWords = "\\b(ducker|mother ducker|motherducker|shot|bad word|another bad word|)\\b"
// Courtesy of Martin R
// https://stackoverflow.com/users/1187415/martin-r
private func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [.caseInsensitive])
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
public func cleanUp(_ string: String) -> String {
let dirtyWords = matches(for: self.dirtyWords, in: string)
if dirtyWords.count == 0 {
return string
} else {
var newString = string
dirtyWords.forEach({ dirtyWord in
let newWord = String(repeating: "😲", count: dirtyWord.characters.count)
newString = newString.replacingOccurrences(of: dirtyWord, with: newWord, options: [.caseInsensitive])
})
return newString
}
}
}
Usage:
yourLabel.text = ProfanityFilter.sharedInstance.cleanUp(yourString)
Extension checking for foul language.
Swift 4.2
Example Usage:
"poop".containsBadWord()
Extension:
extension String {
func containsBadWord()->Bool {
//Sorry for bad words
let badWords = ["insert","bad","words","here","poop"]
for word in badWords {
if lowercased().contains(word) {
return true
}
}
return false
}
}
I would suggest looking into an API to which you can submit a string and get a JSON response containing information such as:
Is the string bad?
Total # of bad words contained in string
An array containing all recognized bad words
A censored version of the input string
I found a couple sources via Google. Check these out and do a little more research to find if an API is the best fit for you and which one you should use. I would assume that using an API like the one I have listed below would be the most practical approach, as you would NOT have to compile a list of "bad" words yourself and use resources from the device to sort through the list (which can contain thousands of words).
Rather, you can simply submit a string using the API to get a network response containing the data in JSON format from the API server.
Why not let the API Server do the logic for you and just spit out an answer?
NeutrinoAPI
If this method returns a range,
str.range(of: "darn|crap|newb", options: [.regularExpressionSearch, .caseInsensitiveSearch], range: str.startIndex..<str.endIndex, locale:nil)
an offensive word has been found. While this method can be used to remove the offending strings:
str.replacingOccurrences(of: "darn|crap|newb", with: "", options: [.regularExpressionSearch, .caseInsensitiveSearch])
If you filter bad words locally you wouldn’t easily accommodate many languages, also as new bad words appear, you would have to waste a developers time manually putting in bad words, alternatively, there are apis designed for this purpose: https://www.moderatecontent.com/documentation/badwords

Returning a processed String of an Enum on Swift iOS

I was just creating a localization module for my project, and as I'm new to Swift I had an idea of if the following was possible.
I have an enum like this:
enum Localizations : String
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
With this enum I can have the localized string with this command:
let myString = Localizations.StringId1.localized
But when you have to put lots of strings the .localized it's like redundant as you already have Localizations before.
So what I'm looking for is if I can do something like this:
let myString = Localizations.StringId1
And myString would be something like "Press Button to Continue"
I have managed to do something working, but not in all cases.
Found in this link: https://appventure.me/2015/10/17/advanced-practical-enum-examples/
On the 'Advanced Enum Usage Protocols' Step it suggests a modification like the following would get what I want:
protocol CustomStringConvertible {
var description: String { get }
}
enum Trade: CustomStringConvertible {
case Buy, Sell
var description: String {
switch self {
case Buy: return "We're buying something"
case Sell: return "We're selling something"
}
}
}
let action = Trade.Buy
print("this action is \(action)")
// prints: this action is We're buying something
My modifications are theses ones:
protocol CustomEnumString {
var localized: String { get }
}
enum Localizations : String, CustomEnumString
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
But when printing it if shows me the enum literal, and when passing to a function the compilers tells me it's invalid:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: StringId1"
// note: AlertStrings is an struct with two strings
// this fails to compile saying that cannot convert value of type 'Localizations' to expected argument type 'String'
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
// this one works, but it's not the purpose I had in mind
let alertStrings = AlertStrings(title: Localizations.StringId1.localized, message: Localizations.StringId2.localized)
So... in short, I would like to be able to do this:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: Press Button To Continue"
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
But in the enum I only would like to specify literals once, not first on case's and later inside a switch.
Thanks in advance!
phelgo, A member of NSBarcelona just told me about this, and works perfectly!
enum Localizations {
static let StringId1 = NSLocalizedString("string_to_translate_1", comment: "")
}
let myString = Localizations.StringId1
It may look unfamiliar to have an enum with no cases, but we get to
keep all of its safety (and code completion), while still preventing
Localizations from being instantiated by mistake (if it was a
struct)
Conform your enum to CustomStringConvertible and rename localized to description:
enum Localizations: String, CustomStringConvertible
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var description : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
let alertStrings = AlertStrings(title: Localizations.StringId1.description, message: Localizations.StringId2.description)

swift text with 2 variables and different language

i would like to translate a string, which have two variables inside.
at the moment, i use for translating this code:
NSLocalizedString("Name_In_Langauge_String_File",comment:"")
but how can i translate the following string?
This is a test with 100 Pictures and 50 Users
where 100 and 50 are variables.
Put this in you Localizable.strings:
"Name_In_Langauge_String_File" = "This is a test with %d Pictures and %d Users";
and in your code:
String.localizedStringWithFormat(
NSLocalizedString("Name_In_Langauge_String_File",
comment: ""),
pictures,
users)
In a project I was working on I noticed that we kept repeating the code to do the string formatting for the localization file. This meant you could not just use the value, you first needed to check what parameters were required. One way to avoid this problem is to use Swift enums. This method is also useful for unit testing your localizations.
Assume you have the following 3 localizations in your strings file:
"TestNoParams" = "This is a test message";
"TestOneParam" = "Hello %#";
"TestTwoParams" = "This is a test with %d Pictures and %d Users";
Now you can use the following enum, protocol and extension to reference your strings:
protocol LocalizationProtocol {
var key: String { get }
var value: String { get }
}
extension LocalizationProtocol {
private func localizationValue() -> String {
return NSLocalizedString(key, comment:key)
}
private func localizationValueWithFormat(parameters: CVarArgType...) -> String {
return String(format: localizationValue(), arguments: parameters)
}
}
enum Localizations: LocalizationProtocol {
case TestNoParams
case TestOneParam(name: String)
case TestPicturesAndUsers(pictures: Int, users: Int)
var key: String {
switch self {
case .TestNoParams: return "TestNoParams"
case .TestOneParam: return "TestOneParam"
case .TestPicturesAndUsers: return "TestTwoParams"
}
}
var value: String {
switch self {
case .TestOneParam(let name):
return localizationValueWithFormat(name)
case .TestPicturesAndUsers(let pictures, let users):
return localizationValueWithFormat(pictures, users)
default:
return localizationValue()
}
}
}
Now to use it you just need to call the enums value method:
let testNoParam = Localizations.TestNoParams.value
let testOneParam = Localizations.TestOneParam(name: "users name").value
let testTwoParams = Localizations.TestPicturesAndUsers(pictures: 4, users: 500).value
The example I have shown is simplified, but you can also nest enums to provide a nice grouping for your localizations. For instance you could have your enums nested by ViewController. This is an example for a welcome message: Localizations.Main.WelcomeMessage.value

Resources