Хабраюзер afan в своём топике Идея: функция форматирования для удобной локализации строк предложил интересную концепцию. Какой собственно я решил воспользоваться в своём движке.
И так,
Конечно как честный человек, я прочитал все комменты данного топика и нашёл этот список формул. Чтобы добавить поддержку нужного языка, просто скопируйте формулу из списка.
Затем я решил поискать готовые решения. Нашёл решение от PawnHunter [C#] IFormatProvider для счетных существительных. Оно интересно плюс имеется функция записи числительных прописью. Но недостаток в сложности добавления языков. Автор изначально привязался к русскому и ищет три формы числа по правилу русского. С английским ему повезло, так как первые 2 формы совпадает, а третья не нужна. Любой другой язык с отличной формулой и количеством форм даст сбой.
Дальше я не искал, потому что материала для своего решения было достаточно: идея, формулы и готовый пример.
Синтаксис использования.
C#:
JavaScript
Как видите синтаксис взят с исходной статьи, только форматирование подогнано под String.Format. Плюс сокращённый вариант позаимствован у PawnHunter.
Символ % отвечает за подстановку числового значения.
Обратите внимание на следующее. В расширенном варианте словоформ передаётся на одну больше для нулевого значения, которая идёт первой. В сокращенном нулевой формы нету, для неё форма высчитывается по формуле.
Для использования спец-символов, таких как | % их нужно продублировать || %%. Для сокращённой формы плюс ((и )).
И в конце привожу исходные коды.
______________________
И так,
Конечно как честный человек, я прочитал все комменты данного топика и нашёл этот список формул. Чтобы добавить поддержку нужного языка, просто скопируйте формулу из списка.
Затем я решил поискать готовые решения. Нашёл решение от PawnHunter [C#] IFormatProvider для счетных существительных. Оно интересно плюс имеется функция записи числительных прописью. Но недостаток в сложности добавления языков. Автор изначально привязался к русскому и ищет три формы числа по правилу русского. С английским ему повезло, так как первые 2 формы совпадает, а третья не нужна. Любой другой язык с отличной формулой и количеством форм даст сбой.
Дальше я не искал, потому что материала для своего решения было достаточно: идея, формулы и готовый пример.
Синтаксис использования.
C#:
NumeralsFormatter formatter = new NumeralsFormatter(); // культура текущего потока
formatter.CultureInfo = new CultureInfo( "ru-RU" ); // установить свою культуру
Int32 files = 0, folders = 5;
String exResult = String.Format( formatter, "{0:W|Не найдено файлов|1 файл найден|% файла найдено|% файлов найдено} в {1:W|0 папках|1 папке|% папках|% папках}.", files, folders ); // расширенная запись
String shortResult = String.Format( formatter, "{0:W|% файл(,а,ов) найден(,о,о)} в {1:W|% папк(е,ах,ах}.", files, folders ); // сокращённая запись
JavaScript
// текущая культура берётся из переменной CultureInfo.name
var files = 0, folders = 5;
var exResult = String.wformat( "{0:W|Не найдено файлов|1 файл найден|% файла найдено|% файлов найдено} в {1:W|0 папках|1 папке|% папках|% папках}.", files, folders ); // расширенная запись
var shortResult = String.wformat( "{0:W|% файл(,а,ов) найден(,о,о)} в {1:W|% папк(е,ах,ах}.", files, folders ); // сокращённая запись
Как видите синтаксис взят с исходной статьи, только форматирование подогнано под String.Format. Плюс сокращённый вариант позаимствован у PawnHunter.
Символ % отвечает за подстановку числового значения.
Обратите внимание на следующее. В расширенном варианте словоформ передаётся на одну больше для нулевого значения, которая идёт первой. В сокращенном нулевой формы нету, для неё форма высчитывается по формуле.
Для использования спец-символов, таких как | % их нужно продублировать || %%. Для сокращённой формы плюс ((и )).
И в конце привожу исходные коды.
using System;
using System.Text;
using System.Globalization;
public class NumeralsFormatter : IFormatProvider, ICustomFormatter
{
public CultureInfo CultureInfo;
public NumeralsFormatter() : this( System.Threading.Thread.CurrentThread.CurrentCulture ) { }
public NumeralsFormatter( CultureInfo cultureInfo )
{
this.CultureInfo = cultureInfo;
}
public Object GetFormat( Type formatType )
{
return formatType == typeof( ICustomFormatter ) ? this : CultureInfo.GetFormat( formatType );
}
private String FormatUnknown( String format, Object arg, IFormatProvider formatProvider )
{
IFormattable formattable = arg as IFormattable;
if( formattable == null ) return arg.ToString();
return formattable.ToString( format, formatProvider );
}
public Boolean IsNumeralType( Type type )
{
return type == typeof( Int32 )
|| type == typeof( UInt32 )
|| type == typeof( Double )
|| type == typeof( Single )
|| type == typeof( Decimal )
|| type == typeof( Int64 )
|| type == typeof( UInt64 )
|| type == typeof( Int16 )
|| type == typeof( UInt16 )
|| type == typeof( Byte )
|| type == typeof( SByte )
|| type == typeof( Char );
}
public String Format( String format, Object arg, IFormatProvider formatProvider )
{
if( format == null || !IsNumeralType( arg.GetType() ) )
return FormatUnknown( format, arg, formatProvider );
String[] forms = format.Replace( "||", "\ufffc" ).Split( '|' );
if( forms[0].ToUpper() == "W" )
{
Int32 idxForm = -1;
Double n = Math.Abs( Convert.ToDouble( arg ) );
// translate.sourceforge.net/wiki/l10n/pluralforms
switch( CultureInfo.Parent.Name )
{
case "en": idxForm = ( n != 1 ? 1 : 0 ); break;
case "ru": idxForm = ( n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ); break;
}
String form = null;
if( idxForm != -1 )
{
if( forms.Length > 2 ) // extended form
{
idxForm = n == 0 ? 1 : idxForm + 2;
form = forms[idxForm].Replace( '\ufffc', '|' );
}
else if( forms.Length > 1 )
{
StringBuilder sb = new StringBuilder();
forms = forms[1].Replace( '\ufffc', '|' ).Replace( "((", "\ufd3e" ).Replace( "))", "\ufd3f" ).Split( '(' );
int i = 0;
for( ; i < forms.Length - 1; ++i )
{
sb.Append( forms[i] );
Int32 rp = forms[i + 1].IndexOf( ')' );
if( rp != -1 )
{
sb.Append( forms[i + 1].Substring( 0, rp ).Split( ',' )[idxForm] );
forms[i + 1] = forms[i + 1].Substring( rp + 1 );
}
}
sb.Append( forms[i] );
form = sb.Replace( '\ufd3e', '(' ).Replace( '\ufd3f', ')' ).ToString();
}
}
if( form != null )
return form.Replace( "%%", "\ufffc" ).Replace( "%", ( (IFormattable)arg ).ToString( null, CultureInfo ) ).Replace( '\ufffc', '%' );
}
return FormatUnknown( format, arg, formatProvider );
}
}
String.wformat = function(f)
{
var a=arguments;return f.replace(/{(\d+)(.*?)}/ig,function($0,$1,$2)
{
var arg=a[Number($1)+1];
var n = Math.abs( arg );
if( $2 && !isNaN(n) )
{
var forms = $2.replace(/\|\|/gm,'\ufffc').split( '|' );
if( forms[0].toUpperCase() == ":W" )
{
var idxForm = -1;
switch( CultureInfo.name.split('-')[0] )
{
case "en": idxForm = ( n != 1 ? 1 : 0 ); break;
case "ru": idxForm = ( n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ); break;
}
var form = null;
if( idxForm != -1 )
{
if( forms.length > 2 ) // extended form
{
idxForm = n == 0 ? 1 : idxForm + 2;
form = forms[idxForm].replace( /\ufffc/gm, '|' );
}
else if( forms.length > 1 )
{
var sb = [];
forms = forms[1].replace( /\ufffc/gm, '|' ).replace( /\(\(/gm, '\ufd3e' ).replace( /\)\)/gm, '\ufd3f' ).split( '(' );
var i = 0;
for( ; i < forms.length - 1; ++i )
{
sb.push( forms[i] );
var rp = forms[i + 1].indexOf( ')' );
if( rp != -1 )
{
sb.push( forms[i + 1].substring( 0, rp ).split( ',' )[idxForm] );
forms[i + 1] = forms[i + 1].substring( rp + 1 );
}
}
sb.push( forms[i] );
form = sb.join('').replace( /\ufd3e/gm, '(' ).replace( /\ufd3f/gm, ')' );
}
}
if( form != null )
return form.replace( /%%/gm, '\ufffc' ).replace( /%/gm, arg ).replace( /\ufffc/gm, '%' );
}
}
return arg;
});
}
______________________