Pull to refresh

Реализация С#, Javascript: функция форматирования для удобной локализации строк

Reading time12 min
Views1.6K
Хабраюзер afan в своём топике Идея: функция форматирования для удобной локализации строк предложил интересную концепцию. Какой собственно я решил воспользоваться в своём движке.

И так,

Конечно как честный человек, я прочитал все комменты данного топика и нашёл этот список формул. Чтобы добавить поддержку нужного языка, просто скопируйте формулу из списка.

Затем я решил поискать готовые решения. Нашёл решение от 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;
        });
}



______________________
Текст подготовлен в Хабра Редакторе от © SoftCoder.ru
Tags:
Hubs:
Total votes 8: ↑7 and ↓1+6
Comments16

Articles