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

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.

Related

How to find the i18n key from the value

I want to make a helper function which can take an English string as input and translate it to the desired language chosen by the user.
As all the locale files would be having key in common so I am looking for a way to find key using the string value. I am using default.yml files for storing translations.
After finding the key, I can use <%= t() %> for translating key to other languages.
I don't know how your yaml file looks like and it probably depends on what kind of I18n backend you use. If it's just key-value pairs for every language then something like this could work
I18n.backend.translations[:en].key "English string"
There is also the simple way. Just load the yaml file with the translations.
YAML.load_file("path_to_translations.yml").key "English string"

How to perform language transformation(Localization ) in UITable View Content in swift

i Need to convert my iOS app into arabic .i am done with converting UIComponents text but when it comes to UItabelview,i am not able to do .i am not getting how to convert text which is in an array in swift 3
You must localize your app to support different languages.
Please refer to this Medium Tutorial to get a clear idea on how to localize an app.
If you have an array of display strings that come from an API then you have a problem. If those strings are static and come from a known set of strings then you could create entries in your localizable.strings file that map each of those strings to their Arabic equivalent, and use NSLocalizedString() to convert them at runtime:
let sourceString = cellData[indexPath.row].titleString
let localizedString = NSLocalizedString(sourceString, comment: "String from API")
Ask your backend developer to add arabic string in response and then use below code:
cell.yourLabel = NSLocalizedString(YourArray[indexPath.row].arabicString, comment: "")

iOS localizable string with default values

I want to understand how localizable base string works in iOS. For example, in Android if I have got a default localizable file strings (base localization on iOS) like:
"title_app" = "Title"
"Copy" = "Copy";
"Edit = "Edit;"
And then I have got a Spanish localizable file like:
"Copy" = "Copiar";
"Edit" = "Editar";
Why on iOS if I set Spanish language on my device the key "title_app" doesn't appear? Because Android if doesn't find a key, it takes the key from the default language.
Sometines there are words that they don't need a translation. Or sometimes I have 10 languages and maybe one language needs a translation from a non translatable word. For example "title_app" = "My app". It will be the same in English, French, Italian, Spanish... but in chinese no. It is not efficient write the key on 10 files, repeating... imagine 10, 20 o 50 words.
Always Apple/iOS is far behind in matters of translation/localization compared to Android... :S
Talking about translation; sorry my bad english.
The NSLocalizedString macro takes two parameters, a key and a comment. The key will be looked up in Localizable.strings file, which is a simple key-value pair collection.
As #TheEye pointed out in the comments via the blog post link, you can exercise more control, and explicitly include a default value by using the NSLocalizedStringWithDefaultValue macro instead.
What you have to realize is that you have a choice: you can either use the simpler macro and treat your key as the default value (and iOS will fall back to that) or use the more specialized macro that is more verbose but gives you more control instead.
Note, this has already been discussed here: Fallback language iOS (with incomplete Localizable.strings file)

Where to define long message strings with parameters

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.

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

Resources