Different font for parameters in NSLocalizedString - swift

I want to give a label a text that have multiple fonts in it. This can be accomplished by creating a NSMutableAttributedString. However, I am not sure how I format the following case:
String(format: NSLocalizedString("%# has replied in '%#'", comment: ""), username, conversationTitle)
I want to give the username and conversation title a separate font. I want to do this in the less buggiest way. What I mean by this:
I do not want to find out the username later on in the string by using a substring. This is causing issues when the conversationTitle is the same as the username, or the conversationTitle is in the username etc. etc..
I do not want to build up the string, as seen here: https://stackoverflow.com/a/37992022/7715250. This is just bad when creating NSLocalizedString's, I think the translators are going to have a bad time when string are created like that.
Questions like: Making text bold using attributed string in swift, Are there approaches for using attributed strings in combination with localization? and others are mostly string literals without NSLocalizedString or NSLocalizedString with parameters.

First, you should have in your .strings a much more generic and readble key, something like:
"_REPLIED_IN_" = "%# has replied in %#";
Do not confuse keys and values as you seem to do in your example.
Also, it's easier later to see when there is an hardcoded string not localized in your code.
Now, there is an issue, because in English, it might be in that order, but not necessarily in other languages.
So instead:
"_REPLIED_IN_" = "%1$# has replied in %$2#";
Now, I'll use the bold sample, because it's easier, but what you could do is use some custom tags to tell you that it needs to be bold, like HTML, MarkDown, etc.
In HTML:
"_REPLIED_IN_" = "<b>%1$#</b> has replied in <b>%$2#</b>";
You need to parse it into a NSAttributedString:
let translation = String(format: NSLocalizedString(format: "_REPLIED_IN_", comment: nil), userName, conversationTitle)
let attributedText = NSAttributedString.someMethodThatParseYourTags(translation)
It's up to you to choose the easiest tag format), according to your needs: easy to understand by translators, and easy to parse (CocoaTouch already has a HTML parser, etc.).

Related

A Problem with Plural Localization in Swift

I have a function to get a localized string for plural cases.
extension String {
static func localizedStringForPlurals(
formatKey key: String,
comment: String = "",
args: CVarArg... ) -> String
{
let format = NSLocalizedString(key, comment: comment)
var result = withVaList(args){
(NSString(format: format,
locale: NSLocale.current,
arguments: $0) as String)
}
return result
}
...
}
The key is NSLocalizedString, which gets a localized string for the plural case from the stringsdict file.
I have an example of how to get localized string for taps. If the int number is one, the localized string is "This tap"; if int is larger than one, i.e., others, the string is "These taps". See attachment: stringsdict file (English).
It works fine for English. For example:
var count: Int
...
let locS = String.localizedStringForPlurals(
formatKey: "theseTaps", args: count)
// "This tap" if count is 1
// "These taps" if count is > 1
However, it does not work well for Chinese. Here is a similar stringsdict file for Chinese.
The result in Swift code is always a plural string, i.e., "这些点击"(These tap), even if the count is 1 (expected: This tap or 这个点击)
I am not sure if this is a bug in NSLocalizedString or not. I know that in Chinese in general, there are no plural cases. However, as in this example, there are plural cases, this tap or these tap. In Chinese, there is no plural for "taps", one word "tap" can refer to (one) tap or (>1) taps. but "this" and "these" in Chinese are different, and they mean plural cases.
I do like Apple's localization for the plural framework. In Swift code, there is no awareness of what language is being used, but the localized string is picked up from the localization framework. However, I would like this framework to work as a developer's expectation, as in my example in two dict files. Not to be too smart enough to always take the plural result, assuming that there is no plural rule in a language (like in Chinese).
I am not sure if there is way to resolve the issue? For example, turn off this kind of smart way (plural rule for a specific language. zero, one, others for English, others for Chinese, ...), and let developer to decide and layout the plural rule of how to get a localized string.
To answer your question -- if you want something else, you need to program it yourself. In fact, I did do this at Trello because we wanted English to have a "zero" where we said something like "You don't have any cards" instead of "You have 0 cards".
We had a function like yours and in it, it said (pseudocode)
if (locale == "en" && numberForPlurals == 0) {
pluralCategory = "zero";
}
And we looked up the string ourselves based on the key and plural category.
Another option is to reword the phrase so that it works for any number -- this is obviously not ideal. An English example would be "You have 3 cat(s)" -- which is what you would do if you didn't have a pluralization capability.
You seem to know this, but for those that need more information:
Apple is just implementing the UNICODE/CLDR standard for pluralization -- this is not iOS specific -- it's a standard.
Not every language plurals based on 1, or 2+ like English does. Those categories you see are not really referring to the numbers -- they are categories that each language defines for itself.
I wrote an article about it here: https://www.atlassian.com/engineering/ios-i18n-plurals (note: this article and the implementation was written before plurals were supported on iOS directly)
Summary
There are six categories of numbers that each language could use: zero, one, two, few, many and other
You have to look up your language here: https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html
Then see what the mapping from number to category is -- For example: english uses one and other: https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html#en
Chinese uses only one category (other) and doesn't pluralize based on numbers: https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html#zh
So, you should only expect a Chinese translator to provide one string (just for other) and that's the only one you need. If you need something else, you have to write your own logic and function to provide the key to look up in the strings file.
NOTE: the names "zero", "one", "two", etc are misleading for most languages. There are six categories -- they could have just named them cat1, cat2, cat3, etc.

How to display an int without commas?

I have a list of Text views that include a year saved as an int. I'm displaying it in an interpolated string:
Text("\($0.property) \($0.year) \($0.etc)")
The problem, it adds a comma to the int, for example it displays 1,944 instead of the year 1944. I'm sure this is a simple fix but i've been unable to figure out how to remove the comma. Thanks!
There is explicit Text(verbatim:) constructor to render string without localization formatting, so a solution for your case is
Text(verbatim: "\($0.property) \($0.year) \($0.etc)")
Use Text(String(yourIntValue)) if you use interpolation you need to cast it as a string directly. If you allow the int to handle it, it shows with a ,.
So to recap.
let yourIntValue = 1234
Text(String(yourIntValue)) // will return `1234`.
Text("\(yourIntValue)") // will return `1,234`.
I use the built-in format parameter. It's useful for formatting well beyond just this one specific usage (no commas).
Text("Disk Cache \(URLCache.shared.currentDiskUsage,
format: .number.grouping(.never))"))

How to display Icon from package using name from String?

I want to display Icon based on it's name parsed from external source. Now i have several newsfeeds integrated in app, like this:
myPages.add(new Subpage(0, "Main", FontAwesome5Regular.newspaper, "http://url.one"));
myPages.add(new Subpage(1, "Buses", MaterialCommunityIcons.bus_multiple, "http://url.one"));
where 3-d argument of SubPage constructor is IconData. 
I want to generate so much pages as needed, based on CSV. I want to place in CSV lines like
0, Main, FontAwesome5Regular.newspaper, "http://url.one"
1, Buses, MaterialCommunityIcons.bus_multiple, "http://url.two"
I have no problems with parsing csv, but I don't understand how to convert parsed String "FontAwesome5Regular.newspaper" to IconData needed by constructor of Subpage.
It would be great to get solution without async/await, catching error, etc, cause I'm really sure, that CSV contains no errors, all strings are valid, all classes are available
Thank you for any ideas!
You could use 'dart:mirrors' library for that, but, unfortunately, library isn't available in flutter, so you can't access classes' static properties using their names as string. You can do it like this:
IconData getIconData(String str) {
switch (str) {
case "FontAwesome5Regular.newspaper": return FontAwesome5Regular.newspaper;
case "MaterialCommunityIcons.bus_multiple": return MaterialCommunityIcons.bus_multiple;
default: return null;
}
}
great workaroud from Richard Heap:
Can you change your CSV? Let's say you want to send an umbrella icon. Rather than "MaterialIcons.beach_access" send "MaterialIcons" in one column and "60222" in another. Parse the 60222 into a int: var codePoint = int.parse(cp); and make your icon as var icon = IconData(codePoint, fontFamily: ff);
– Richard Heap 2 hours ago

How would I manipulate a string to display different parts?

All,
I have a string in a dictionary that's in an array that I need to manipulate a bit. The string returns: TEST-TEST-ABC_Dry_Cleaning-R12-01.
Here's what I need to do with it:
I need to pull out "ABC_Dry_Cleaning" and change it to "ABC Dry Cleaning" (no underscores)
I need to delete "TEST-TEST-"
I need to pull out "R12" and put that in a different string
I need to add "01" on to the end of "ABC Dry Cleaning" (looks like "ABC Dry Cleaning (01)")
How would I do these general things? There is much more that needs to be done, but once I know the way to do these tasks I can change it around for the others as needed. NOTE: "ABC_Dry_Cleaning" could be just "Red_Cups" or "McDonalds_Bag" - basically, a count of characters won't work.
Thanks!
If you're on iOS4+, consider using a regular expression to match the segments that you're interested in retaining. Take a look at the NSRegularExpression class.
You can then use the matches to build up the final string.

Extracting data(strings) from a string large string

A long time ago I had to extract data from a string, and I went with a while loop that went through the whole string char by char extracting bits of data that I need. It wasn't very efficient but it worked.
In my latest app I would like to try and do it in the way that a good engineer would do it. Are there ways to search the string for an expression? or a sub string maybe?
For example out of the html in the string, there is a line that will contain a team name.
<td width="25%"><span class="teamname">Blue Bombers</span></td>
Is there a call I can do that would find the "teamname" and then extract the teamname from between the > <.
I could go char by char saving the last 10 chars to a string until the string equals "teamname", then keep going until i hit the > save everything i get until i again hit a <. but i guess thats taking the easy inefficient way.
Many Thanks
-Code
You can get the range of string "class" using NSRange, then do your logic... it will probably reduce the character searching..
Your code should be like follows,
if ([substring rangeOfString:#"class"].location != NSNotFound) {
// "class" was found
else {
// "class" was not found
}
If that's the only part of the string you're interested in and then just find a starting point like "teamname" via -rangeOfString:. If there's more than one occurrence then make repeated calls with -rageOfString:options:range:.
If you need more comprehensive parsing, however..
If this string is actual XHTML then you may be able to use one of the various XML parsers, e.g. TouchXML, and then find what you need via DOM lookups. However if (as seems likely) it's not pure XHTML then this is unlikely to help. In that case you might try loading up the HTML in an offscreen UIWebView and using JavaScript calls to find specific elements.