Created attachment 16089 [details]
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:
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
Mono 4.4.0 (mono-4.4.0-branch/fcf7a6d) (64-bit)
GTK+ 2.24.23 (Raleigh theme)
Package version: 404000148
=== NuGet ===
=== Xamarin.Profiler ===
=== Xamarin.Android ===
Version: 184.108.40.206 (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:
=== Xamarin Android Player ===
Location: /Applications/Xamarin Android Player.app
=== Apple Developer Tools ===
Xcode 7.3 (10183.3)
=== Xamarin.iOS ===
Version: 220.127.116.118 (Xamarin Enterprise)
Build date: 2016-05-13 17:19:05-0400
=== Xamarin.Mac ===
Version: 18.104.22.1689 (Xamarin Enterprise)
=== Xamarin Inspector ===
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
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).
It's also unclear why the implementation has a limit of 9 parameters.
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).
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".
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.
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.
More complex than I thought originally, moving to C8
Any update on this, please?
What's the status of this issue?
@John the System.EntryPointNotFoundException issue is now tracked in
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.
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.
@Sebastien thanks for the update although it took quite long
PR merged in https://github.com/xamarin/xamarin-macios/commit/f5df902049cb1b9e37e6f6e038f004f0589c1d6a
Keeping open until new API or updated docs are available for NSString API