I have a bunch of the following line of code:
[NSString stringWithFormat:#"%# and %#", subject.title, secondsubject.title];
[NSString stringWithFormat:#"%# and %d others", subject.title, [newsfeeditem count] - 1];
and a lot more in the app. Basically I am building a news feed style like facebook where it has string constants. blah liked blah. Where/how should I do these string constants so it's easy to do for internationalization? Should I have a file just for storing string constants?
See the String Resources section of the Resource Programming Guide. The key section for this particular problem is "Formatting String Resources."
You'd have something like:
[NSString stringWithFormat:NSLocalizedString(#"%1$# and %2$#", #"two nouns combined by 'and'"),
subject.title, secondsubject.title];
The %1$# is the location of the first substitution. This lets you rearrange the text. Then you would have string resource files like:
English:
/* two nouns combined by 'and' */
"%1$# and %2$#" = "%1$# and %2$#";
Spanish:
/* two nouns combined by 'and' */
"%1$# and %2$#" = "%1$# y %2$#";
You need to be very thoughtful about these kinds of combinations. First, you can never build up a sentence out of parts of sentences in a translatable way. You're almost always need to translate the entire message in one go. What I mean is that you can't have one string that says #"I'm going to delete" and another string that says #"%# and %#" and glue them together. The word order is too variable between languages.
Similarly, complex lists of things can cause all kinds of headaches due to various agreement rules. Some languages have special plural rules, gender agreements, and similar issues. As much as possible, keep your messages simple, short, and static.
But the above tools are very useful for solving the problem you're discussing. See the docs for more details.
Related
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.
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.).
Going through number of examples regarding to NSLocalizedString, what I found was we need to pre-define all the string in Localized.string file for what-ever language you want to localize. But, is it possible to localize dynamic string. My idea was, I am displaying few text in UILabel that i get after web request. It means the string is now dynamic in nature.
Declare in Localizable.strings
"SAMPLE_LOCALIZE_STRING" = "This is sample dynamic localize string for %#.";
Use it like this
NSString *dynamicStr = #"Test";
label.Text = [NSString stringWithFormat:NSLocalizedString(#"SAMPLE_LOCALIZE_STRING", nil), dynamicStr];
If those strings are fixed ones (I mean a limited number of options) then pre-store them in the localized string file.
If not, I would suggest to add a parameter to your request that would indicate the language and then the server would return string in that language.
I have handled this situation as follows,
Include language in the request. For ex: http://yourIp/language/notesandcondition
The webservice should be designed to handle for different languages.
[NSString stringWithFormat:NSLocalizedString(#"Table View Cell Row %d", #""), indexpath.row];
Question: How would one write a function to check and return whether or not a string (NSString) contains a valid zip code worldwide.
Additional info: I am aware of RegEx in iOS. However I am not so fluent at it. Please keep in mind this should accepts anything valid in any country as true.
Examples
US - "10200"
US - "33701-4313"
Canada - "K8N 5W6"
UK - "3252-322"
etc.
Edit: Those who voted down or to close the question, please do mention why. Thank you.
^[ABCEGHJKLMNPRSTVXY]\d[A-Z][- ]*\d[A-Z]\d$
Matches Canadian PostalCode formats with or without spaces (e.g., "T2X 1V4" or "T2X1V4")
^\d{5}(-\d{4})?$
Matches all US format ZIP code formats (e.g., "94105-0011" or "94105")
(^\d{5}(-\d{4})?$)|(^[ABCEGHJKLMNPRSTVXY]\d[A-Z][- ]*\d[A-Z]\d$)
Matches US or Canadian codes in above formats.
UK codes are more complicated than you think: http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom
I suggest you don't do this. I've seen many websites that try to enforce zipcodes, but I've never seen one get it right. Even the name zipcode is specific to the US.
In other words:
- (BOOL)isValidZipCode: (NSString *)zip {
return YES;
}
I was originally going to write [zip length] > 0, but of course even that isn't guaranteed.
Each country that uses postcodes/zip codes usually has their own format. You are going to be hard-pressed to find a regular expression that matches any worldwide code!
You're better off adding a country picker that determines the regular expression (if any) to be used to validate the zip code.
As an aside, the postcode you have given as a UK example is not correct. A decent UK regex is:
^(^gir\\s0aa$)|(^[a-pr-uwyz]((\\d{1,2})|([a-hk-y]\\d{1,2})|(\\d[a-hjks-uw])|([a-hk-y]\\d[abehmnprv-y]))\\s\\d[abd-hjlnp-uw-z]{2}$)$
I enabled fts in sqlite for iphone and tried this and works, although very slow:
SELECT field FROM table_fts WHERE replace(replace(replace(replace(replace(lower(field), 'á','a'),'é','e'),'í','i'),'ó','o'),'ú','u') LIKE replace(replace(replace(replace(replace(lower('%string%'), 'á','a'),'é','e'),'í','i'),'ó','o'),'ú','u')
But it does not work when I want to use MATCH, it does not bring me results and there is no error
SELECT field FROM table_fts WHERE replace(replace(replace(replace(replace(lower(field), 'á','a'),'é','e'),'í','i'),'ó','o'),'ú','u') MATCH replace(replace(replace(replace(replace(lower('string'), 'á','a'),'é','e'),'í','i'),'ó','o'),'ú','u')
Is there any error or is there any other approach where I can make a tilde insensitive search?. I looked answers in the web with no success.
Two approaches:
First, you can violate normal-form and add columns to your table containing ASCII-only representation of your searchable fields. Furthermore, before doing a search against this secondary search column, you also remove international characters from the string that being searched for, too (that way you're looking for ASCII-only string in a field with the ASCII-only representation).
By the way, if you want a more general purpose conversion of international characters with ASCII, you can try something like:
- (NSString *)replaceInternationalCharactersIn:(NSString *)text
{
NSData *stringData = [text dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
return [[[NSString alloc] initWithData:stringData encoding:NSASCIIStringEncoding] autorelease];
}
Second, you could presumably use sqlite3_create_function() to write your own function (that presumably invokes a permutation of the above) that you can use right in your SQL statements themselves. See the SQLite documentation.
Update:
By the way, given that you're doing FTS, the sqlite3_create_function() approach is probably not possible, but it strikes me that you could either do FTS on the field containing the ASCII-only string, or write your own tokenizer that does something along those lines.