iOS how to localize files in for a module - iphone

I have a class that implements the "Take photo / Choose from library" that we all know and love. It is here https://github.com/fulldecent/FDTake This is included in my other projects via git submodule and that works fine.
Now I need to translate the text in that class to Chinese so it is "拍照 / 选照片" or something like that. Is there a good way to put translations in there so everyone can use them?

Localization is typically handled by the NSLocalizedString(<#key#>, <#comment#>) macro. In the source your replace all hard coded string with the macro. For example:
[self.buttonTitles addObject:#"Hi"]; // hard coded greeting
with
[self.theLabel setText:(NSLocalizedString(#"theKey", #"Hi"))];
Then genstrings (from inside a Terminal) is used to scan the implementations file (*.m) and to write its output to the language project folder (here: en.lproj)
$ genstrings -o en.lproj/ *.m
In the directory en.lproj/ a file called Localizable.strings. Its contents will be:
/* Hi */
"theKey" = "theKey";
The comment /* Hi */ is taken from our source code. The string Hi should be displayed to the (English speaking) user. So we need to edit the string on the right hand side of the equals sign and make it the greeting, = "theKey" has to become = "Hi":
/* Hi */
"theKey" = "Hi!";
So far so good
All this is fine if there are only a few strings or when there is no intend to ever modify the strings. The moment gestrings is run again it will overwrite the modifications and you effectively lose the work done in Localizable.strings.
An idea could be to write the output of genstrings to a different location. But then you will have to manually merge the changes. Once the Localizable.strings file grows it becomes a nightmare trying to keep source code and Localizable.strings in sync. So lets try to avoid that.
A big help comes from using NSLocalizedStringWithDefaultValue(<#key#>, <#tbl#>, <#bundle#>, <#val#>, <#comment#>). This macro will allow to set a default value in the Localizable.strings file and in addition, it will make the need for the initial edit of the value field go away.
Putting it together:
[self.theLabel setText:NSLocalizedStringWithDefaultValue(#"theKey2", #"Localizable", [NSBundle mainBundle], #"Hi!", #"informal greeting"))];
After running the genstrings command as used above there is now a small different in Localizable.strings
/* informal greeting */
"theKey2" = "Hi!";
Apart from the comment now telling the translator that we want an informal greeting, the “Hi!” is already present in the value filed. There is no need to go to the Localizable.strings file, search for the correct line, modify the field form “theKey” to “Hi!”. genstrings did that for us based on the default value supplied with NSLocalizedStringWithDefaultValue.
Add the file Localizable.strings to the xcode project.
Doing the translations
After changing the source code, for a new language from inside the Xcode first add a localization to Localizable.strings. Xcode will generate a copy of Localizable.strings under a subfolder based on the original Localizable.strings.
I personally don't speak Chinese, but German. So if to add German localization my translation would go under de.lproj/Localizable.strings, italian under it.lproj/ and so on.
Edit the new Localizable.strings as needed:
(German)
/* informal greeting */
"theKey2" = "Hallo!";
(Italian)
/* informal greeting */
"theKey2" = "Ciao!";
and then build and run.
********* begin edit
Bundle it up
The above considers a "standard" xcode project. You are asking about cereateing a module, therefore allowing your code to become an addition to a project. I suggest you create a bundle with the localizations. When somebody will include your code into their project the localizations remain separate.
Full documentation about bundles is here.
A project that uses bundles for localizations is QuincyKit (there probably are more, that one was the first that came to mind)
So when placing the localization into a bundle other than the mainBundle the [NSBundle mainBundle] in the line below has to change
[self.theLabel setText:NSLocalizedStringWithDefaultValue(#"theKey2", #"Localizable", [NSBundle mainBundle], #"Hi!", #"informal greeting"))];
Instead of getting the strings from the mainBundle, obtain a reference to your own module. The docs suggest:
NSBundle* myBundle = [NSBundle bundleForClass:[self class]];
So the line becomes:
[self.theLabel setText:NSLocalizedStringWithDefaultValue(#"theKey2", #"Localizable", myBundle, #"Hi!", #"informal greeting"))];
********* end edit
PS: : my original text can be seen here

I think a bundle (as Olaf suggests) would work great, but another way with less overhead to ensure that your localizable string resources don't interfere with another project in the same solution (causing a weird problem to people reusing your component in another localized project) is to change your localizable.strings file name to a unique file name. This means that where you used NSLocalizedString you now need to use its variant NSLocalizedStringFromTable (Apple documentation), where tableName is the same name as your strings file (without the .strings extension). You can define your own macro so that's it's just a straight replacement of NSLocalizedString with, say FDTakeLocalizedString.
A file name collision is much less likely to happen with XIBs files or storyboards than for the localizable.strings file. But in both cases, if you use a prefixed naming convention (say FDTake.strings and FDTake-Main.xib), it will eliminate the risk and can only help.

Related

How to localise CL applications in macOS (Xcode)?

I am having trouble localising a command-line application I've created in Xcode. Here's what I did:
Used NSLocalizedString on all strings to be translated
Add base localisation, and the target localisation under Project settings
Add Strings file, check both English, and target language checkboxes
Create strings to be localised via genstring (both English and target language) and localised them as necessary
When I build the application, I still get all the text in English. What could be the issue here? My system is set to my target language, and when I try to add a scheme with the target language, I get the following error:
Error: 2 unexpected arguments: '(language_code)', 'NO'
This error is only present if I use swift-argument-parser for the application. For plain applications, it's still not possible to get the localised version.
Xcode 11.3, macOS10.15.3
Usually when you build a Cocoa application (with NSApplicationMain), the framework will take care of loading the appropriate localized strings from the main bundle. The framework will parse UserDefaults overrides from command line arguments, so you/Xcode can supply locale settings via flags like -AppleLanguages and -AppleLocale .
When you write a command-line utility, by default there is no bundle, no startup logic that performs localization, and no parsing of UserDefaults. Setting the target language from the scheme just adds the arguments (which is why you get an error from swift-argument-parser). However, you can load resources from a bundle yourself.
// Get the language from UserDefaults (to support scheme settings, will also need to parse command line arguments)
let language = UserDefaults.standard.stringArray(forKey: "AppleLanguages")?.first ?? "en"
// In addition to the command-line binary, you'll also need a bundle with the translations somewhere
let path = "/path/to/\(language).lproj"
let lproj = Bundle(path: path)
let localized = lproj?.localizedStringForKey("key", value: nil, table: nil)
Note that Bundle.main for an unbundled command line utility will just be the parent directory of wherever the binary is. So you can also use NSLocalizedString if your lproj files are built/installed in the appropriate place, but this would be unusual for utilities installed to binary directories like /usr/local/bin.

Cannot localize my Swift 5 framework. Localization import does nothing

I develop a Swift 5 framework that runs on iOS 12 and above for iPhone and iPad.
I added French localization in addition to English, but the project info tab shows there's no localized files for both languages as shown on the image below:
When I run Xcode Editor|Export for localization I get the bundled files (including the fr.xliff file). I’ve edited fr.xliff with Xcode to translate some strings, and imported it back. But nothing changed in the app.
Btw, I can’t see any of the *.strings files in the project files hierarchy, which sounds weird.
[Update]
So I manually added Localizable.Strings file, and added English and French.
When I import xliff files, the Localizable.strings files remains empty. No apparent issue pops up.
When I put the key=value list in the string file, the NSLocalizedString() does not return the right localised version of the string (it always return the key).
Help appreciated.
Found what's wrong: I had to use the full (extended) version of NSLocalizedString() in order to make the function call get the translation from my framework Localizable.strings files (rather than the client application), as follows:
NSLocalizedString("Continue", tableName: tableName, bundle: bundle, comment: "Continue in the button title of UIAladdinOnboardingViewController")
Where:
tableName: a let global that hold the name of my localisable string file (in my case "Aladdin.Localizable"). I did this to avoid potential collision of .strings files (as my framework name is Aladdin);
bundle: a let global in my the framework name space to designates the framework model bundle to pick the string from. I defined it like this: let bundle = Bundle(for: ExtendedAppDelegate.self) where ExtendedAppDelegate is one of my framework custom class.
Side note of importance:
Btw, don’t put your interpolating string as the key parameter of the NSLocalizedString call, otherwise it’s gonna be interpolated at runtime, leading to an interpolated string that won't exist in your string file.
Don’t do that:
let s = NSLocalizedString("{Start your \(introductoryPeriod) free trial.}", tableName: tableName, bundle: bundle, comment: "in price display")
Do this:
let s = String(format: NSLocalizedString("{Start your %# free trial.}", tableName: tableName, bundle: bundle, comment: "{Start your \\(introductoryPeriod) free trial.}"), introductoryPeriod)
With the corresponding Aladdin.Localizable.strings (en):
/* {Start your \(introductoryPeriod) free trial.} (in price display) */
"{Start your %# free trial.}" = "{Start your %# free trial.}";
To generate the strings:
I used the gestrings from the terminal as follows:
$genstrings -o en.lproj/ *.swift
$genstrings -o fr.lproj/ *.swift
And so on for every target languages you have.
Finally, do the translation straight in the string files.
in Localization you can Localize your string directly from Storyboard.
after adding your language..click on Main.storyboard -> go to identity and type...
you can see your localize languages string...Image of your Main.strings -> select them select all Languages..
after that you can see main.strings(French) and main.strings(English) in your Navigation panel...
change your strings on prefer languages like...
Image of how to change your strings in English
Image of how to change your strings in French
now, change Your Simulator Language from Edit schema -> Run -> Your language(French or English).
it's working..

Swift NSLocalizedString practical use

I couldn't understand and find any sources that showed me the practical use of NSLocalizedString. Even Apple documentation..
NSLocalizedString(<#key: String#>, tableName: <#String?#>,
bundle: <#NSBundle#>, value: <#String#>, comment: <#String#>)
Ok so lets say I created this in a file, how do I write this for different languages?
Do I create a localized version of the whole file like: en.thisfile.swift , es.thisfile.swift, fr.thisfile.swift?
Or do I create new languages in different bundles or tables?
NSLocalizedString("hello", tableName: "en", value: "hello", comment: "wtf is this anyways?")
NSLocalizedString("hello", tableName: "fr", value: "bonjour", comment: "wtf is this anyways?")
NSLocalizedString("hello", tableName: "es", value: "hola", comment: "wtf is this anyways?")
And then how do I use it in a random file after setting it up?
println("dat nslocalizedstring I created, which will automatically know what to write??")
The table is the name of the file that defines the string, for example, table "ErrorMessages" will define a file called ErrorMessages.strings.
If no table is specified, the file will be the default Localizable.strings. The format of the file is a list of the following:
/* This is displayed when the entity is not found. */
"MSG_object_not_found" = "Object not found. Please, try again.";
Where the first line is the comment, the value on the left side is the key that identifies the string and the value on the right side is the actual localized value.
Usually, you can use the English value as the key (the 2 parameter version of NSLocalizedString). The language name (en, fr, es) is not part of the method call.
All of this is there to make translation simple. Note that most translators won't understand or even see the application, that's why it's important to have a good comment to provide a context for the message. There are examples of funny translation all over the internet which were created because the context was missing and the translators didn't know what they were translating.
Basically, your code will contain the texts in your main language and then you will generate the .strings files from your code using genstring tool (See this for more info). Then you will give the files to your translator, add the translated files to your application (every language in a different folder) and the application will select the correct files at runtime depending on the language selected by the user.

URLForResource alway returns nil

I created a project without checking "use CoreData". The Project name is "glossary"
Now I changed my mind and so for I added an data model usinng Add->New File->Resource->Data Model->gave it the name Glossary->didn`t select any class->Finish.
The next step was to design my Data Model.
Then I added the needed Code to my AppDelegate.
For all Steps i was following this Tutorial:
https://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/iPhoneCoreData01/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40008305-CH1-SW1
My problem now is located in this line:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"glossary" withExtension:#"momd"]
modelURL is always nill
Creating an extra NSBundle Ref and calling mainBundle shows me that this is working, however i don`t know if it is really the right path.
Doing the same Steps on an project with selected "use CoreData" while creating works great.
Has anybody an idea?
It has to do with model versioning. You want to add a versioned model (item in Xcode project tree will have .xcdatamodeld extension) and then your code will run smoothly.
In Xcode 4 when you add a new Core Data file it is versioned by default. Even if you added Core Data support after project creation.
If you don't plan to have model versions, just use .mom in your code.
I'm not 100 % sure, but URLForResource does work for files only and momd is directory.
URLForResource documentation: The method first looks for a matching resource file in the nonlocalized resource directory of the specified bundle. (In Mac OS X, this directory is typically called Resources but in iOS, it is the main bundle directory.) If a matching resource file is not found, it then looks in the top level of any available language-specific “.lproj” directories. (The search order for the language-specific directories corresponds to the user’s preferences.) It does not recurse through other subdirectories at any of these locations. For more details see Bundles and Localization.

Multiple Localizable.strings files in one iOS app

Is it possible to have multiple components that have their separate localizations in an app?
For example I want to localize strings in my app but also use ShareKit which is localized itself (so my bundle will contain 2 Localizable.strings in different locations).
So, ShareKit contains folders like en.lproj and de.lproj that contain Localizable.strings files and I want to create the same for the rest of my project but keep them separate.
I have tried just keeping them separate and that resulted in the strings of my app not being used. Any ideas?
There is a way to do this that is quite simple.
There are several macros for localizing, one of which is NSLocalizedStringFromTable(). This takes three parameters:
The string to localize
The table name, which is just a file name
The comment, just like in NSLocalizedString()
If you use a table name then you can have another strings file, i.e. if I do NSLocalizedStringFromTable(#"name", #"MyStrings", #"whatever"); then put my strings in MyStrings.strings it will use that.
See Matt Gallagher's post about this.
every file/resource can be localizable, also the IB .xib files or images jpg/png,
you just need to select a file in your project, open info, go in the "general" tab/section and press the "make file localizable"...
then you can add any language you want, xCode will create a new file duplicating the first one and will put it in the right folder...
luca
It is not possible to do what you are asking. You can have multiple Localizable.strings files, but only one per language in their respective .lproj folders.
I understand that you do not want to edit the ShareKit Localizable.strings files because that would be a pain when updating but I think you need to look at how much work it would actually take. Depending on how many languages you need to support it may be less work to localize your own strings and add them to the bottom of the ShareKit Localizable.strings files than to implement your own localization scheme.
BTW, to detect what language the device is currently set to you can use:
NSArray *preferedLocalizations = [[NSBundle mainBundle] preferredLocalizations];
This gives you an array of two letter language code strings. The item at index 0 is the currently selected language. This could be helpful if you need to make logic decisions based on the language.
oh ok, I don't know ShareKit, but i guess you cannot edit its text files and just add your new words to that files, isn't it?
then you may consider to create your own "dictionary" file and try to localize it, then read it and put it in a NSDictionary where you can read the single words/value...
you can insert a record in your file "myDict.plist" with key "greeting" of type String with value "ciao" for the italian one and "hallo" in the english one
NSString *bundle = [[ NSBundle mainBundle] pathForResource:#"myDict" ofType:#"plist"];
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:bundle];
NSString *greetingsTranslatedInTheGoodLanguage = [savedStock objectForKey:#"greeting"];
// just to try what you have read:
NSLog (#"translated by your own dictionary: %#", greetingsTranslatedInTheGoodLanguage);
[savedStock release];
luca
ps
read the comments too...