Bug 41292 - NSString.LocalizedFormat does not return the correct string
Summary: NSString.LocalizedFormat does not return the correct string
Status: CONFIRMED
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll (show other bugs)
Version: XI 9.8 (tvOS / C7)
Hardware: PC Mac OS
: Normal normal
Target Milestone: Future Cycle (TBD)
Assignee: Sebastien Pouliot
URL:
Depends on:
Blocks:
 
Reported: 2016-05-24 16:04 UTC by John Miller [MSFT]
Modified: 2018-03-07 17:04 UTC (History)
7 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample Project (7.62 KB, application/zip)
2016-05-24 16:04 UTC, John Miller [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 41292 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
CONFIRMED

Description John Miller [MSFT] 2016-05-24 16:04:22 UTC
Created attachment 16089 [details]
Sample Project

**Overview:**

   When trying to use NSString.LocalizedFormat for returning the correct string for localized plurals, it does not return the correct string. It instead returns the format string itself, like when nothing is found.

**Steps to Reproduce:**

   1. Run the attached app on an iOS simulator

   Note* It seems there is another bug in XI because an exception is thrown when trying to execute the method:
   "System.EntryPointNotFoundException: xamarin_localized_string_format_1"

   That will need to worked around/fixed before being able to reproduce the above issue.

**Build Date & Platform:**

=== Xamarin Studio Enterprise ===

Version 6.1 (build 817)
Installation UUID: e01c3049-a2d2-4e0a-aad8-afe6fb627c4d
Runtime:
	Mono 4.4.0 (mono-4.4.0-branch/fcf7a6d) (64-bit)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 404000148

=== NuGet ===

Version: 3.3.0.0

=== Xamarin.Profiler ===

Not Installed

=== Xamarin.Android ===

Version: 6.1.99.224 (Xamarin Enterprise)
Android SDK: /Users/johnmiller/Library/Developer/Xamarin/android-sdk-macosx
	Supported Android versions:
		2.3   (API level 10)
		4.0.3 (API level 15)
		4.1   (API level 16)
		4.2   (API level 17)
		4.4   (API level 19)
		5.0   (API level 21)
		5.1   (API level 22)
		6.0   (API level 23)

SDK Tools Version: 25.1.1
SDK Platform Tools Version: 23.1
SDK Build Tools Version: 23.0.2

Java SDK: /usr
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

Android Designer EPL code available here:
https://github.com/xamarin/AndroidDesigner.EPL

=== Xamarin Android Player ===

Version: 0.6.5
Location: /Applications/Xamarin Android Player.app

=== Apple Developer Tools ===

Xcode 7.3 (10183.3)
Build 7D175

=== Xamarin.iOS ===

Version: 9.8.0.318 (Xamarin Enterprise)
Hash: efefc1e
Branch: cycle7
Build date: 2016-05-13 17:19:05-0400

=== Xamarin.Mac ===

Version: 2.9.0.719 (Xamarin Enterprise)

=== Xamarin Inspector ===

Version: 0.8.1.0
Hash: 95792d1
Branch: master
Build date: Thu May 12 22:20:04 UTC 2016

=== Build Information ===

Release ID: 601000817
Git revision: 2335763551f9db8296b08542035977b899b7f3b7
Build date: 2016-04-25 10:45:36-04
Xamarin addins: 7f8c9ab2a981143a87fbd5adbde3f5890a838fde
Build lane: monodevelop-lion-cycle8-preview

=== Operating System ===

Mac OS X 10.11.2

=== Enabled user installed addins ===

Xamarin Inspector 0.8.1.0

**Additional Information:**

   This article helps explain the native behavior expected: http://maniak-dobrii.com/understanding-ios-internationalization/. Scroll to the section title "How -[NSString initWithFormat:locale:arguments] picks correct string from .stringsdict?". 

   It is thought that there is an issue with XIs implementation because of the string->NSString and back again conversion. The data that is carried inside NSString is lost, which causes the native code to not be able to find the correct value (per the article).
Comment 1 John Miller [MSFT] 2016-05-24 16:05:36 UTC
It's also unclear why the implementation has a limit of 9 parameters. 

https://github.com/xamarin/xamarin-macios/blob/2edb2ae4f5bb371a7006731987717c01f8725420/src/Foundation/NSString.cs#L311
Comment 2 Sebastien Pouliot 2016-05-24 18:25:21 UTC
There seems to be several things wrong, or at least incorrect, here.

First the call used by XI is not identical to

> -[NSString initWithFormat:locale:arguments]

It maps to `localizedStringWithFormat` which does _not_ take a NSLocale argument. In retrospect that might not have been the best choice as this is a variadic method

> + (instancetype)localizedStringWithFormat:(NSString *)format, ...

and that cause ABI issues - which is the answer for comment #1


The nice thing is that we should be able to use

> - (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList

which is not variadic and remove comment #1 limitation (and a lot of other code).


Notes:

1. the current managed code (see LocalizedFormat) lacks null checks so it's NRE prone and that needs to be fixed (and unit tested);

2. the missing symbols error might be because it's not compiled part of libxamarin, I assume is works with C6 otherwise the bug report would be different.

3. I'm not sure this covers the original question of the bug, i.e. why it returns the "format string itself".
Comment 3 Ruben Buniatyan 2016-05-25 17:32:53 UTC
After filing this bug to the support, I spent almost a week on this issue and eventually managed to make it work.

The problem with Xamarin's current implementation consists of two different problems:

1. The NSBundle.LocalizedString() method returns System.String. To be able to use .stringsdict files, this method *must* return NSString instead of System.String as the returned NSString instance is actually an instance of __NSLocalizedString which contains the data from the .stringsdict file (that's why currently it returns the format string itself as underlying formattind data is gone). Then we can pass it to localizedStringWithFormat: or initWithFormat:arguments: methods to format the final string according to the .stringsdict.

2. The current NSString.LocalizedFormat() method should be rewritten as its current implementation is completely useless. As under the hood it calls localizedStringWithFormat: which is a variadic function, there's a problem with passing arguments. NSString.LocalizedFormat() displays argument's pointer value instead of the object's value the pointer points to. For example, calling NSString.LocalizedFormat("%d", 2) displays something like "2,099,595,808" (0x7D254A20) which is the IntPtr arg's value passed to the one of the xamarin_localized_string_format_n methods you currently use.

Thus, I replaced the call to localizedStringWithFormat: with non-variadic initWithFormat:arguments: (or initWithFormat:locale:arguments:) eliminating the argument number limitation and making it work by directly calling 
localizedStringForKey:value:table: to obtain the required NSString instance to pass it to initWithFormat:locale:arguments: method.
Comment 4 CraigD 2016-06-09 19:06:22 UTC
D'oh - I just spent ages trying to get stringsdict working this before finding this bug report. Agree 100% with Ruben's comment #3. Will add sample and docs when fixed.
Comment 5 Sebastien Pouliot 2016-06-22 15:24:02 UTC
More complex than I thought originally, moving to C8
Comment 7 Ruben Buniatyan 2016-12-09 17:02:02 UTC
Any update on this, please?
Comment 8 Ruben Buniatyan 2017-05-18 21:39:13 UTC
What's the status of this issue?
Comment 9 Sebastien Pouliot 2018-01-19 01:25:17 UTC
@John the System.EntryPointNotFoundException issue is now tracked in
https://github.com/xamarin/xamarin-macios/issues/3265
and a workaround is provided (e.g. add `--nofastsim`)


@Ruben as you found out variadics can be quite tricky. 

While getting general purpose calls works across all platforms/ABI/compilers is not easy it's even worse, in this case, because of the use of formatters. Here the managed types need to be marshalled so it won't match the formatter in native code. This is why:

> NSString.LocalizedFormat("%d", 2) 

shows as a pointer because the current code use `id` which means everything must be an `NSObject` - not primitive types. If you turn this into an `NSNumber` then it should print fine (works for me).

However it makes it even harder to use the API... we cannot just turn all `%?` to `%@` (for `NSObject`) this still won't cover cases like `%.2f` (and all other special cases that the formatter can accept, now or in the future).

With some local changes and if I use `@` then I get

```
1 player is waiting
2 players are waiting
```

using updated API (exposing NSString instead of string), e.g.

```
		var format = NSBundle.MainBundle.GetLocalizedString ("TestKey", null, null);
		Debug.WriteLine (NSString.GetLocalizedString(format, (NSNumber)1));
		Debug.WriteLine (NSString.GetLocalizedString(format, (NSNumber)2));
```

I'll clean up what I have but since you already have something working you might want to keep it since my version will have limitations that might not fit your specific needs.
Comment 10 Sebastien Pouliot 2018-01-19 02:28:59 UTC
PR master https://github.com/xamarin/xamarin-macios/pull/3266

This adds correct API for NSBundle to return Ann NSString (instead of an `System.String`).

I might (or not) continue to fix the variadic API but, once GH#3265 is fixed, the original issue will be solved.
Comment 11 Ruben Buniatyan 2018-01-19 13:34:37 UTC
@Sebastien thanks for the update although it took quite long
Comment 12 Sebastien Pouliot 2018-01-20 19:03:02 UTC
PR merged in https://github.com/xamarin/xamarin-macios/commit/f5df902049cb1b9e37e6f6e038f004f0589c1d6a

Keeping open until new API or updated docs are available for NSString API