Localized app won't display other language

I'm trying to localize my first app, before I set about doing them localized all as I rewrite them in Swift. I must be doing some setting wrong, as everything looks right, but when run in the simulator the label texts do not change. I am coding on a MacBook Pro 2016, with macOS 10.12.3, using XCode 8.2.1 and targeting iOS 10.2 universal.


To clarify the situation, I made the simplest possible single view app, with one label named 'label1'. Base Internationalization is checked. I set the label text as follows in the view controller 'view did load':


label1.text = NSLocalizedString("label1Text", value: "Hello World", comment: "Main label")


Then I created .strings file Localizable.strings with this code:


"label1Text" = "Hello World";


Then in Project: Info, I added language French. XCode created Main.strings (French) with the following:


/ Class = "UILabel"; text = "Hello"; ObjectID = "ufk-lo-b4J"; */

"ufk-lo-b4J.text" = "Bonjour le monde"; (I inserted the French)


If I look at the storyboard in Assistant per Apple suggestions, and do 'preview', the text label is displayed in French.


I create a scheme 'testFrench' and set the options to 'French' , run (release), device to iPhone 7 plus, and run.

However, when the app runs in the simulator (or if loaded on my iPhone 7 plus set to French language), the text field displays in English. This is the same result I'm getting with my bigger app. Everything seems in place, but the text fields are not translated.


There must be a step or setting I am missing, but I cannot find anything to clarify this in the Apple docs, and there are no sample codes or tutorials I can find using XCode 8.2.1 and Swift 3.1 posted.

Replies

I set the label text as follows in the view controller 'view did load':

label1.text = NSLocalizedString("label1Text", value: "Hello World", comment: "Main label")

Then I created

.strings
file
Localizable.strings
with this code:
"label1Text" = "Hello World";

What’s the purpose of these steps? And what happens if you skip them?

There are two basic steps in localisation:

  • Strings that are in your UI resources (xibs or storyboards) are localised by localising those UI resources. In your case, because you’re using base localisation, you don’t need separate copies of those UI resources. Rather, the UI picks the localised strings from a separate

    .strings
    file (in your example,
    Main.strings
    ).
  • Strings in your code are localised via a call to

    NSLocalizedString
    . This reads the string from a
    .strings
    file (by default this is
    Localizable.strings
    ) and that
    .strings
    file needs to be localised for each language.

You seem to be mixing up the two approaches, using both the UI and the code approach for something that only needs the UI approach. So, if you drop the code approach, I suspect things will just work.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

You must be a zen master, as your answers are cryptic.


If I understand what you advise, I should not create Localizable.string. So I started over to test what I think you are telling me.


New project. Single view. Add a label to the only view, named "greetingLabel". Connect.


Don't do as previously advised in so many localization tutorials. Don't create Localizable.strings. Don't set text of object in View Did Load by assigning it via NSLocalizedString.


Then add languages, which XCode then creates main.string(language) files. In file inspector, check 'English' language (which oddly is not checked by default)


Then, those text fields automatically get filled in? Hard to believe.


So I did this, and for the first time, the French label text came up! Halleluja!


The question I must ask you, Eskimo, is why this was so hard to get to, why did it take so many hours of research? Why doesn't Apple have any up to date simple code samples that illustrate this, or a clear explanation for newbies to localization? Maybe be you guys have been spending too many hours under florescent lights and forgotten how to explain things to non-experts? Just kidding. Thanks for the help. It really does just work, but only if you know the secret of how to get it to just work, just like in Lord of the Rings.

Uh-oh. Though this worked with a simple one label app, when I do the exact same thing to my existing Swift app, though it generates the main.string files for English and French, the label and button titles don't get changed, and no explanation why, other than this:


2017-02-07 22:30:01.971 travelclockxl[89802:5727811] CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 17. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.


Eskimo, can you explain this? This is a newly written in Swift version of my app. Don't know where an 'old-style plist parser' would come from.

Old-style plist parser: missing semicolon in dictionary on line 17. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.

This indicates that there’s a syntax error in a property list file, which is probably one of the

.strings
files you localised (a
.strings
file is a actually property list file in a old school format, hence the “Old-style plist parser” in the error message). Try this:
  1. Find all the

    .plist
    and
    .strings
    files in your project
  2. Run

    plutil
    in ‘lint’ mode over each one

Here’s an example using the TextEdit sample code.

$ cd Desktop/TextEdit
$ find /Applications/TextEdit.app -name '*.plist' -print0 | xargs -0 plutil -lint
/Applications/TextEdit.app/Contents/Info.plist: OK
/Applications/TextEdit.app/Contents/Resources/TextEdit.help/Contents/Info.plist: OK
/Applications/TextEdit.app/Contents/Resources/TextEdit.help/Contents/version.plist: OK
/Applications/TextEdit.app/Contents/version.plist: OK
$ find /Applications/TextEdit.app -name '*.strings' -print0 | xargs -0 plutil -lint
/Applications/TextEdit.app/Contents/Resources/ar.lproj/DocumentProperties.strings: OK
/Applications/TextEdit.app/Contents/Resources/ar.lproj/Edit.strings: OK
/Applications/TextEdit.app/Contents/Resources/ar.lproj/EncodingAccessory.strings: OK
/Applications/TextEdit.app/Contents/Resources/ar.lproj/InfoPlist.strings: OK
/Applications/TextEdit.app/Contents/Resources/ar.lproj/LinePanel.strings: OK
/Applications/TextEdit.app/Contents/Resources/ar.lproj/Localizable.strings: OK
… and so on …

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you, Quinn,


I did as you suggested, and got this answer that I do not understand in regard to the Main.strings (French) file:


2017-02-08 08:02:33.222 plutil[90312:5831608] CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 17. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.

/Users/mel/Desktop/totest/inhere/Main.strings: Unexpected character / at line 2


The message does confuse me a bit about whether it is a missing semicolon on line 17, or an unexpected character on line 2


Here are the contents of that file:


/ Class = "UILabel"; text = "Alarm"; ObjectID = "4ch-v7-vzQ"; */

"4ch-v7-vzQ.text" = "Réveil";


/ Class = "UIButton"; normalTitle = "Test"; ObjectID = "OAD-eA-J1f"; */

"OAD-eA-J1f.normalTitle" = "Essai";


/ Class = "UILabel"; text = "Set"; ObjectID = "Xrv-fl-ALy"; */

"Xrv-fl-ALy.text" = "Fixé";


/ Class = "UIButton"; normalTitle = "Done"; ObjectID = "g2w-oa-Mc6"; */

"g2w-oa-Mc6.normalTitle" = "Terminé";


/ Class = "UIButton"; normalTitle = "SET"; ObjectID = "iu6-Lf-G4u"; */

"iu6-Lf-G4u.normalTitle" = "FIXÉ;


/ Class = "UISegmentedControl"; mQg-IE-hNX.segmentTitles[0] = "12"; ObjectID = "mQg-IE-hNX"; */

"mQg-IE-hNX.segmentTitles[0]" = "12";


/ Class = "UISegmentedControl"; mQg-IE-hNX.segmentTitles[1] = "24"; ObjectID = "mQg-IE-hNX"; */

"mQg-IE-hNX.segmentTitles[1]" = "24";


Hmm. I just cannot see what it is that it does not like. All the semicolons appear to be there. What character might be unexpected? Could it have to do with copy/paste from Google Translate of Réveil? This is important for me, as whatever error creeped in here could occur again as I redo my more complex apps.

OK. Something was wrong with the semicolon after "FIXÉ;. Ah, it's obvious: the close " is missing. Duh. Looking carefully in XCode, I could see that the semicolon was highlighted in red, very faint. For some reason, XCode did not put up a yellow or red warning about the missing semicolon, which I suppose it should.


Once that was fixed, voila! All just works.


One other lesson I learned: my 'HIDE' button initially did not have a default title set in Attributes Inspector. That is a problem, as then when you add a language, that button is left out of the .strings files. That may be an XCode bug, I don't know. But you must be sure to set a default title.


The fact that XCode automatically makes localizing all these object titles this easy is amazing and wonderful. I just wish somewhere in the documentation or code samples this was made clear, as it would have saved me perhaps 10 hours and some of Quinn's most valuable (truly!) time. Apparently there was a time not long ago when you had to add a Localizable.strings file. It is not obvious that that conflicts with the automatic workings.

I have run into another issue.


The above works fine for UI objects. What is unclear to me is how to get any string localized. For example, when someone presses the 'Test' button, I change the text to 'Stop' (playing the alarm sound).


I presumed that this code would do the trick:


stopLocal = NSLocalizedString("stopLocal", comment:"Fill in localized text for Stop")


combined with adding this code to Main.strings (French): "stopLocal" = "Arrêtez";


Nope. This just results in the text in the button being replaced with "stopLocal" There must be another way, but I cannot find it. This is the only other missing piece.


Note: Since writing this, I have found how to fix this. Using the above: NSLocalizedString("stopLocal", comment:"Fill in localized text for Stop") seems to be correct, but it appears you cannot just write this in the .strings file yourself and expect it to work.


Instead, following the rather clear advice at this link:


https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/LocalizingYourApp/LocalizingYourApp.html#//apple_ref/doc/uid/10000171i-CH5-SW1


to export an XLIFF file, which you can open in XCode and edit, then re-import, the magic is done and it just works. It results in pretty much the same language in the .strings file, but apparently does some needed internal connection.


By the way: I would have been happy to be pointed to a clear explanation of how to do both of these things, but I cannot find such on the developer site, nor via searching the web. I think it would be nice of Apple to post such somewhere, or if already there, make it easier to find.

One other problem has popped up. I cannot seem to get the app name to localize. I follow the instructions in the Apple docs, change that in XLIFF and import it, nothing seems to make it change.


I now have InfoPlist.strings (French) with this inside:

/ (No Commment) */

"CFBundleDisplayName" = "Horloge de Voyage";


and InfoOPlist.strings (English) with this:


/ (No Commment) */

"CFBundleDisplayName" = "Travel Clock";


But if you run the app in simulator in French, then go to 'Home', the app name still displays as Travel Clock


Later: UNTIL you find out that in Simulator, running the app in a scheme as French language is not sufficient. You must go into settings on the Simulator, change the language of the simulator to French, and then and only then will the app name change appear! The only challenge then is having a name that will fit. Different problem.


Even later: it turns out that once you get rolling, XCode does a very nice job of populating new languages for you, generating Localizable.strings for each, as well as main.strings and infoPlist.strings. You set the app display name in the infoPlist.string for the language, the storyboard object text in main.strings, and 'user-facing text' (such as strings used within the app) in Localizable.strings. I have learned the hard way that you must let XCode generate these links, or they do not work. There may be some hidden pointers.


This reminds me a bit of the old days 5 years or more ago, at the beginning of iOS, when code signing was arcane, difficult and time-consuming. Now, it is virtually automatic. The hard part here was finding a clear explanation of how to get started. I just may write a tutorial aimed at beginners in localization like me that I could not find, to save others time getting started. Certainly, encouraging localization is beneficial to Apple, and it would be helpful if they provided more entry-level tutorials, especially right away when things are changed.