The way I solved this was indeed to use the CustomFormatter to implement an equivalent of ChoiceFormat which is used by a custom ResourceBundle object to proxy access to the .NET resx files. By implementing a Pluraliser object hierarchy covering all the languages I need (and more in the future) along with ChoiceFormat, I can localise strings accurately.
The code looks a bit like this...
String.Format(new ChoiceFormatProvider(Session["CultureInfo"]), rb.GetString("resxkey", Session["CultureInfo"]), new object[] { 5 });
The strings returned by GetString are localised by the resx file but are then processed by the custom formatter and they look like this....
{0:choice,0#zero str|1#one str|1<more than one}
Taking this further, I can process strings formatted like so...
{0:plural,zero#zero str|one#singular str|two#dual str|few#few str|many#many str|other#other plural str}
These two features coupled with a set of custom pluraliser objects allow for very complex localisation features to be implemented.