Where to define long message strings with parameters - ios

I would like to display some longer messages (>100 characters) with parameters in my iOS application. They would appear in different parts of the application, and would contain information about the state of some processes e.g.
[_labelProgressInformation setText:[NSString stringWithFormat:#"%# is busy.",currentProcess.Name]];
[_labelUserInformation setText:[NSString stringWithFormat:#"Please wait for %# to finish. Make sure that... blah blah blah with instructions",currentProcess.Name]];
The question is where should i define them? I would like to avoid hard coding them in many places. Ideally I'd like to have them in one file, so that I can edit them quickly if it would be necessary. How should i go about this? What is the proper way of defining messages that require parameters?

The best approach to this, also regarding potential multi-language use of your application, would be to use localized .strings files. That way, you can just refer to the corresponding string value using a key and the predefined Xcode macro NSLocalizedString(<key>, <comment>) that takes the key as an NSString and an optional comment for this particular string.
Note that this approach also pays off if you only provide your app in one language, it is generally considered best practice.
An example use of this would look like this:
You have a localization file:
Localizable.strings(English) with an entry:
"hello" = "Hello";
"hello_my_name_is" = "Hello, my name is %#";
Then, in your application wherever you want to use that string you can do something like this following:
helloLabel.text = NSLocalizedString(#"hello", nil); // will write "Hello"
nameLabel.text = [NSString stringWithFormat:NSLocalizedString(#"hello_my_name_is", nil), #"John"]; // will write "Hello, my name is John"
You can read more about localization here or follow this great tutorial.

For this, go for Macros
Just create new header file (NewFile -> Source -> Header File)
define the Macros like
#define PROGESSTEXT(text) ([NSString stringWithFormat:#"%# is busy.",text])
then use it where required like
PROGESSTEXT(#"Helloooooo")
NSLog(#"%#",PROGESSTEXT(#"Helloooooo"));
Result : Helloooooo is busy.

Related

String(localized:) has no separate key and value?

New in iOS 15, we are invited to use this String initializer method to make localizable strings in our Swift code:
init(localized keyAndValue: String.LocalizationValue,
table: String? = nil, bundle: Bundle? = nil,
locale: Locale = .current, comment: StaticString? = nil)
The trouble is that the first parameter is, as the internal name suggests, used for both the key and the value. You can see that from this localized French strings file:
/* Alert message: Report a tap */
"You tapped me!" = "Vous m'avez tapé!";
That resulted from my saying
String(localized:"You tapped me!", comment: "Alert message: Report a tap")
and localizing for French.
That's totally wrong! This is supposed to be a list of key–value pairs; we shouldn't be using the English user-facing text as a key.
For one thing, if we now change the English text in our String(localized:comment:) call, our French translation will break. Also, we would be unable to have different French translations for the same English text used in different contexts.
What are we supposed to do about this?
I regard this as a major bug in String(localizable:). If we were using NSLocalizedString, we would have individual key: and value: parameters. String(localizable:) needs that.
I can think of two workarounds. One is: don't use String(localizable:). Just keep on using NSLocalizedString.
The other is to localize explicitly for English. Instead of entering the English user-facing text as the localized: parameter, enter a key string. Then, to prevent the keys from appearing in the user interface, export the English localization and "translate" the keys into the desired English user-facing text. Now import the localization to generate the correct English .strings files.
(If your development language isn't English, substitute the development language into those instructions.)
Now when you export a different localization, such as French, the <trans-unit> element's id value is the key, to which the translator pays no attention, and the <source> is the English, which the translator duly translates.
To change the English user-facing text later on, edit the English Localizable.strings file — not the code. Nothing will break because the key remains constant.
If you want to separate the key and value you can call String.init(localized:defaultValue:table:bundle:locale:comment:). This allows you to specify a default value to use if the key does not exist in your strings file, and is used as the default translation when using Xcode's Export Localisations feature.
For example:
let alertMessage = String(localized: "alert.message.report-a-tap", defaultValue: "You tapped me!")
// Xcode's Export Localisations generates the following:
"alert.message.report-a-tap" = "You tapped me!";
I was experiencing the same problem using SwiftGen to produce my localized strings and the solution for me was to ensure that my generated localized strings file is inside of the appropriate language folder rather than the separate Generated folder I would otherwise use.
The explicit localization approach using keys is imo the correct way here, I think the parameter name keyAndValue is just misleading.
See another initializer using String.LocalizationValue, for AttributedString:
https://developer.apple.com/documentation/foundation/attributedstring/3867590-init
On that one the parameter is just named key:
init(localized key: String.LocalizationValue, options: AttributedString.FormattingOptions = [], table: String? = nil, bundle: Bundle? = nil, locale: Locale? = nil, comment: StaticString? = nil)
After all that seems to align well with SwiftUI's usage of LocalizedStringKey, where no value is given next to the key too.
Beside the current lack of documentation, I don't understand the need to introduce String.LocalizationValue having the mentioned LocalizedStringKey but at least it seems to be aligned from the way it's done in SwiftUI, but not from the previous localizedString(forKey:value:table) / NSLocalizedString way.
In my recent projects we always used key-based translations even for the development language, so this new initializer would work pretty well. But Apple should change the parameter name and update the documentation accordingly, as they did for AttributedString.

Regex to find and replace in xcode...?

I have a java file which I got from android, this has some hard coded values in it. Basically we have a file which creates an object of type country and adds it to a list.
I wish to attain the same functionality but I am not sure of the reg-ex required to find and replace the file contents.
Basically this is how one of the lines in it looks like...
countries.add(new Country("af", "Afghanistan", 93));
And this is what I wan't it to look like
[countries addObject:[[Country alloc] initWithArray:#[#"af",#"Afghanistan",#"93"]];
Do you think regex can be used for such extensive case..? Or will I have to manually do this for every entry..?
You can do a literal search and capture groups like this and replace them.
Regex: countries\.add\(new Country\("([a-z]+)", "(.*)", (\d+)\)\);
Replacement to do: [countries addObject:[[Country alloc] initWithArray:#[#"$1",#"$2",#"$3"]];
Regex101 Demo

Should I use unique ids or the whole sentences when localizing software?

I need to translate a website to a couple languages, and I've already read how to do it:
Mark strings for translation
Generate messages file
Translate messages file
The problem is, if I use whole sentences as the message ids, in english, for example, then if later I decide to modify the text, I'll have to change it in the code and on each message file . Or I could just change the english translation, but then my english message file will look weird, with translations from english to english which do not match.
Example:
Original: "I don't know what to do."
Translation: "I'm not sure what to do."
An alternative is to use unique message ids such as:
Original: "INDECISION_MESSAGE"
Translation: "I'm not sure what to do."
The advantage is that I can change translations without changing the id and things will still be consistent. But then there is no easy way for a translator to know what the message should be like as there is no context except by looking at the code.
What would you use?
In PHP people normally use the first approach you mentioned. In ASP.NET the second.
I think it's more of a personal taste and a matter of the framework that you use.
Personally I prefer the second option. Since you normally already have the ID/translation pair somewhere in a list, the translator can just take that list to translate.
What framework do you use?

Mapping API error codes to localised strings in iOS

I'm writing an iOS app which download some statistics from our company server. In case of error the APIs provides an error code and an error description. I would like to keep the error description (which is always in english) for the internal log and to map the error codes to some localised strings. Which would be the best approach for solving this problem? I was thinking of executing a mapping using a .plist file,but not 100% sure.
Using a plist file with an NSDictionary is fine, as long as the memory footprint is low. I've done something similar.
However, also be aware of the standard method which is NSLocalizedString and using .Strings files for each language.
Here's an example of how to use NSLocalizedString:
// Set the label using the localized string
self.label.text = NSLocalizedString(#"Select choice:", #"Prompt to make a selection.");
The first part is the key, which you define in the file Localizable.strings. If no entry exists in the strings file, then the key name is used, so I make the key equal the default text. In the example above, if no entry is found for the default language, it will just use the key name, which is #"Select choice:".
Then, you create a Localizable.string file and press the Localize button, then create one for each language. Your spanish one might look like this:
/* Contents of Localizable.strings */
"Select choice:" = "Selecciona la opción:";
Of course, you could have an English one, which would look like this:
/* Contents of Localizable.strings */
"Select choice:" = "Select choice:";
The second parameter to NSLocalizedString() is a comment, which is optional, but Apple provides tools to find all of the NSLocalizedString() entries in your code and generate lines in your Strings resource files for you, complete with the comment.
I'll add that if your API takes a language parameter and returns messages in that language, you can use its available languages like this (Objective C):
NSArray *availableLanguages = #[#"en", #"es"]; // API's available languages
NSString *preferredLanguage = [NSBundle preferredLocalizationsFromArray:availableLanguages].firstObject;
Then pass preferredLanguage to the API.
(The API might even have a call to get available languages that it supports.)
See https://developer.apple.com/library/content/technotes/tn2418/_index.html

Config file format

does anyone knows a file format for configuration files easy to read by humans? I want to have something like tag = value where value may be:
String
Number(int or float)
Boolean(true/false)
Array(of String values, Number values, Boolean values)
Another structure(it will be more clear what I mean in the fallowing example)
Now I use something like this:
IntTag=1
FloatTag=1.1
StringTag="a string"
BoolTag=true
ArrayTag1=[1 2 3]
ArrayTag2=[1.1 2.1 3.1]
ArrayTag3=["str1" "str2" "str3"]
StructTag=
{
NestedTag1=1
NestedTag2="str1"
}
and so on.
Parsing is easy but for large files I find it hard to read/edit in text editors. I don't like xml for the same reason, it's hard to read. INI does not support nesting and I want to be able to nest tags. I also don't want a complicated format because I will use limited kind of values as I mentioned above.
Thanks for any help.
What about YAML ? It's easy to parse, nicely structured has wide programming language support. If you don't need the full feature set, you could also use JSON.
Try YAML - is (subjectively) easy to read, allows nesting, and is relatively simple to parse.

Resources