📜 ⬆️ ⬇️

Message Formatting for Yii :: t ()

This article discusses the formatting features of messages for the Yii 2 framework internationalization system . Basically, this is information from the ICU framework and library documentation, with additional explanations and examples. Most of the information is suitable for any framework that uses the ICU library to internationalize messages. The examples imply the translation of messages from English to Russian ('en-US' => 'ru-RU'). Setting up an internationalization system is not covered in the article.

The translation process consists of 2 parts - getting the translated string from the message source and formatting the message. The message sources can be used the classes yii \ i18n \ PhpMessageSource , yii \ i18n \ DbMessageSource , yii \ i18n \ GettextMessageSource .

For formatting, the classes yii \ i18n \ Formatter and yii \ i18n \ MessageFormatter are used, which use the intl extension, which uses the ICU library, which is described in the ICU User Guide . And there are CLDR Specifications and API Reference .

Yii :: t () is a wrapper for calling I18N :: translate ()
Hidden text
\yii\BaseYii: public static function t($category, $message, $params = [], $language = null) { if (static::$app !== null) { return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language); } else { $p = []; foreach ((array) $params as $name => $value) { $p['{' . $name . '}'] = $value; } return ($p === []) ? $message : strtr($message, $p); } } yii\i18n\I18N: public function translate($category, $message, $params, $language) { $messageSource = $this->getMessageSource($category); $translation = $messageSource->translate($category, $message, $language); if ($translation === false) { return $this->format($message, $params, $messageSource->sourceLanguage); } else { return $this->format($translation, $params, $language); } } 


Parameters in the message can be transferred in 2 ways, through an array and separated by commas. The Yii documentation contains the following examples:
 $username = 'Alexander'; echo Yii::t('app', 'Hello, {username}!', ['username' => $username]); // Hello, Alexander! $price = 100; $count = 2; $subtotal = 200; echo Yii::t('app', 'Price: {0}, Count: {1}, Subtotal: {2}', $price, $count, $subtotal); // Price: 100, Count: {1}, Subtotal: {2} 

Looking at the source code of the functions, it is easy to see that the second example will not work in this form. Such a call can only be used for one parameter, and then only because the function I18N :: format () is cast to the type of array:
 $price = 100; echo Yii::t('app', 'Price: {0}', $price); 

Hidden text
 yii\i18n\I18N: public function format($message, $params, $language) { $params = (array) $params; if ($params === []) { return $message; } if (preg_match('~{\s*[\d\w]+\s*,~u', $message)) { $formatter = $this->getMessageFormatter(); $result = $formatter->format($message, $params, $language); if ($result === false) { $errorMessage = $formatter->getErrorMessage(); Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", __METHOD__); return $message; } else { return $result; } } $p = []; foreach ($params as $name => $value) { $p['{' . $name . '}'] = $value; } return strtr($message, $p); } 


In most cases, it is recommended to always pass values ​​through named parameters, for clarity.
')


Formatting Rules


For parameters, formatting rules may be specified.
 {PlaceholderName, ParameterType, ParameterStyle} //  Yii {argNameOrNumber, argType, argStyle} //  ICU 

For example:
 echo Yii::t('app', 'Price: {price, number, currency}', ['price' => 100]); // Price: $100.00 

The ICU documentation describes 10 types of arguments:
 plural select selectordinal choice number date time spellout ordinal duration 

The first 4 types relate to complex arguments, the rest to simple ones. For simple parts, the argStyle is optional. For complex is required, and each type has its own format.


plural


Documentation Used to handle plural forms. There are 6 predefined keywords: zero, one, two, few, many, other. The value for the other is mandatory, it must always be present in the pattern. Different languages ​​have a different set of keywords available. For the Russian language, this is one, few, many, other; for English one, other. A detailed list of compliance rules can be found in the CLDR specification . Type of cardinal rules defines keywords for the plural, ordinal type for selectordinal.
 $fileCount = 21; echo $fileCount.' '.Yii::t('app', '{fileCount, plural, one{file} other{files}}', ['fileCount' => $fileCount]); //  (messages/ru-RU/app.php) '{fileCount, plural, one{file} other{files}}' => '{fileCount, plural, one{} few{} many{} other{}}' //  // 1  // 2  // 11  // 21  // 0  // 1.5  


For special cases, you can specify an exact value. There should be no space after the '=' sign.
 $fileCount = 2; echo $fileCount.' '.Yii::t('app', '{fileCount, plural, =2{special case files} one{file} other{files}}', [ 'fileCount' => $fileCount ]); // 2 special case files 


The '#' sign in internal messages is replaced with the numeric value of the argument. Note that the value is formatted with respect to locale - a comma for the fractional part, a space for thousands. The system locale, which is set by the setlocale () function, does not affect the ICU library.
 $fileCount = 21; echo Yii::t('app', 'Total {fileCount, plural, one{# file} other{# files}}', ['fileCount' => $fileCount]); //  'Total {fileCount, plural, one{# file} other{# files}}' => ' {fileCount, plural, one{# } few{# } many{# } other{# }}' //  21  //  21,2  //  1 000 000  


For cases where the beginning of a phrase already implies a certain amount, there is an offset parameter. When processed, its value is subtracted from the argument. Exact values ​​are compared before subtraction, keywords after. The '#' sign is always replaced by the result of the subtraction.
 $likeCount = 2; echo Yii::t('app', 'You {likeCount, plural, offset: 1 =0{did not like this} =1{liked this} one{and one other person liked this} other{and # others liked this} }', [ 'likeCount' => $likeCount ]); // You and one other person liked this 

While writing the article, I found a useful link to the Meteor MessageFormat documentation, where you can touch it all in real time.


select


Documentation Used to select phrases for specific keywords, usually for selecting the female / masculine form of the word. The keyword other is required, the rest can be any.
 $name = ''; $gender = 'male'; $city = ''; echo Yii::t('app', '{name} went to {city}', [ 'name' => $name, 'went_gender' => $gender, 'city' => $city, ]); //  '{name} went to {city}' => '{name} {went_gender, select, male{} female{} other{}}  {city}' //     //     //       


Messages in braces can be nested and refer to other arguments. This also works for other types. It is recommended that complex arguments be taken out and written complete sentences within their messages.
Hidden text
 $host = ''; $gender_of_host = 'female'; $num_guests = 3; $message = " {gender_of_host, select, female { {num_guests, plural, =0 {{host} does not celebrate her birthday.} one {{host} invites one guest to her birthday.} other {{host} invites # guests to her birthday.} } } male { {num_guests, plural, =0 {{host} does not celebrate his birthday.} one {{host} invites one guest to his birthday.} other {{host} invites # guests to his birthday.} } } other { {num_guests, plural, =0 {{host} do not celebrate their birthday.} one {{host} invite one guest to their birthday.} other {{host} invite # guests to their birthday.} } } } "; echo Yii::t('app', $message, [ 'host' => $host, 'gender_of_host' => $gender_of_host, 'num_guests' => $num_guests, ]); //  " {gender_of_host, select, female { {num_guests, plural, =0 {{host} does not celebrate her birthday.} one {{host} invites one guest to her birthday.} other {{host} invites # guests to her birthday.} } } male { {num_guests, plural, =0 {{host} does not celebrate his birthday.} one {{host} invites one guest to his birthday.} other {{host} invites # guests to his birthday.} } } other { {num_guests, plural, =0 {{host} do not celebrate their birthday.} one {{host} invite one guest to their birthday.} other {{host} invite # guests to their birthday.} } } } " => " {gender_of_host, select, female { {num_guests, plural, =0 {{host}     .} one {{host}  #     .} other {{host}  #     .} } } male { {num_guests, plural, =0 {{host}     .} one {{host}  #     .} other {{host}  #     .} } } other { {num_guests, plural, =0 {{host}     .} one {{host}  #     .} other {{host}  #     .} } } } " //   3     . //   3     . //      3     . 



selectordinal


This type is similar to the plural, except that it has a different set of allowed keywords. Used to display ordinal numbers and related expressions. For the Russian language, only the word other is supported, so 2 letters cannot be output.
 $n = 3; echo Yii::t('app', 'You are {0, selectordinal, one{#st} two{#nd} few{#rd} other{#th}} visitor', [$n]); // You are 3rd visitor //  'You are {0, selectordinal, one{#st} two{#nd} few{#rd} other{#th}} visitor' => ' {0, selectordinal, other{#-}} ', //  3-  

For some reason, only the numbered parameters work, for the named, a warning appears: "Call to ICU MessageFormat :: format () has failed".


choice


Documentation Used to convert numeric ranges to their corresponding strings. Initially this type was intended for the plural form. However, the rules of many languages ​​are too complex for its capabilities. It is considered obsolete, and instead it is recommended to use plural and select.
The message format splits the numeric range [-∞, + ∞] into several ranges. Each range is associated with a string. The first range is selected, where the value of the argument is not greater than the upper limit, and the corresponding string is returned. The number of the initial range is ignored, which corresponds to -∞. The sign '<' (less_than) excludes the border, the sign '#' (less_than_or_equal) includes the range. Instead of a number, you can specify the sign '∞' (U + 221E), instead of '#' the sign is '≤' (U + 2264). Before the '|' there must be no space, otherwise it is included in the message text of the previous range.
 $fileCount = 1; echo $fileCount.': '.Yii::t('app', '{fileCount, choice, 0 # no files| 1 # one file| 1 < many files}', [ 'fileCount' => $fileCount, ]); // 0  -inf // [-inf] | [1] | (1, +inf] // [-inf, 1) | [1] | (1, +inf] // 0: no files // 1: one file // 2: many files $day = 1; echo $day.' - '.Yii::t('app', '{day, choice, 0 # unknown|1 # Sun|2 # Mon|3 # Tue|4 # Wed|5 # Thu|6 # Fri|7 # Sat|8 # unknown}', ['day' => $day]); // [-inf, 1) | [1, 2) | [2, 3) | [3, 4) | [4, 5) | [5, 6) | [6, 7) | [7, 8) | [8, +inf] // 1 - Sun // 2 - Mon 


number


Documentation Used to display numeric values. You can set the style integer, currency, percent, or specify the pattern manually. The value is formatted taking into account the locale settings - decimal separator, thousands separator, currency sign, etc. By default, numbers up to 3 fractional characters are displayed for numbers.
 $value = 123456.789012; echo Yii::t('app', 'Value: {value, number}', ['value' => $value]); // Value: 123,456.789 echo Yii::t('app', 'Value: {value, number, integer}', ['value' => $value]); // Value: 123,457 $value = 1.23; echo Yii::t('app', 'Value: {value, number, percent}', ['value' => $value]); // Value: 123% 


You cannot influence these parameters through the message line. If you need to output in a different format, it is better to use the specialized function yii \ i18n \ Formatter :: asDecimal ().
 $value = 123456.789; Yii::$app->formatter->decimalSeparator = '.'; Yii::$app->formatter->thousandSeparator = ' '; $formattedValue = Yii::$app->formatter->asDecimal($value); echo Yii::t('app', 'Balance: {value, number} - {formattedValue}', ['value' => $value, 'formattedValue' => $formattedValue]); // Balance: 123,456.789 - 123 456.789 


The format of patterns is best shown by examples. Dropping digits rounds according to the rules of mathematics.
Hidden text
 // 0 - ,   $value = 1234567; echo $value.': '.Yii::t('app', 'Result - {value, number, 000000.0000}', ['value' => $value]); echo '<br>'; // 123: Result - 000123.0000 // 1234567: Result - 1234567.0000 // # - , ,   0 $value = 123.456789; echo $value.': '.Yii::t('app', 'Result - {value, number, ######.####}', ['value' => $value]); echo '<br>'; // 123: Result - 123 // 123.456789: Result - 123.4568 // 1-9 -  $value = 123.333; echo $value.': '.Yii::t('app', 'Result - {value, number, 0.2}', ['value' => $value]); echo '<br>'; // 123.111: Result - 123.2 // 123.333: Result - 123.4 // @ -   $value = 1; echo $value.': '.Yii::t('app', 'Result - {value, number, @@@}', ['value' => $value]); echo '<br>'; // 123.456: Result - 123 // 1.23456: Result - 1.23 // 123456: Result - 123000 // 1: Result - 1.00 //  2  4   $value = 12.3456; echo $value.': '.Yii::t('app', 'Result - {value, number, @@##}', ['value' => $value]); echo '<br>'; // 12: Result - 12 // 12.3: Result - 12.3 // 12.3456: Result - 12.35 // . -   (  ) // , -   ( ) //   ,      $value = 123456.789; echo $value.': '.Yii::t('app', 'Result - {value, number, #,###.##}', ['value' => $value]); echo '<br>'; // 123456.789: Result - 123,456.79 echo $value.': '.Yii::t('app', 'Result - {value, number, #,####.##}', ['value' => $value]); echo '<br>'; // 123456.789: Result - 12,3456.79 //      //        echo $value.': '.Yii::t('app', 'Result - {value, number, #}', ['value' => $value]); echo '<br>'; // 123456.789: Result - 123457 //     2  ( ,  ) $value = 987654321; echo $value.': '.Yii::t('app', 'Result - {value, number, #,##,###}', ['value' => $value]); echo '<br>'; // 987654321: Result - 98,76,54,321 // ; -            //         $value = -12.34; echo $value.': '.Yii::t('app', 'Result - {value, number, #.##;minus # value}', ['value' => $value]); echo '<br>'; // -12.34: Result - minus 12.34 value // E -        $value = 123000000; echo $value.': '.Yii::t('app', 'Result - {value, number, #.##E+00}', ['value' => $value]); echo '<br>'; // 123000000: Result - 1.23E+08 // * -   (padding);   ,   * $value = 1234; echo $value.': '.Yii::t('app', 'Result - {value, number, *_######}', ['value' => $value]); echo '<br>'; // 123: Result - ___123 // 1234: Result - __1234 $value = 1234; echo $value.': '.Yii::t('app', 'Result - {value, number, ######*_}', ['value' => $value]); echo '<br>'; // 123: Result - 123___ // 1234: Result - 1234__ // % -  ;      100 // ‰ -   (U+2030);      1000 $value = 0.123; echo $value.': '.Yii::t('app', 'Result - {value, number, #.#‰}', ['value' => $value]); echo '<br>'; // 0.123: Result - 12.3% // 0.123: Result - 123‰ // ¤ -   (U+00A4) //  -  ,  -   ,  -    // (    ,     ,  ) $value = 12.34; echo $value.': '.Yii::t('app', 'Result - {value, number, #.##¤}', ['value' => $value]); echo '<br>'; // 12.34: Result - 12.34$ echo $value.': '.Yii::t('app', 'Result - {value, number, #.##¤¤}', ['value' => $value]); echo '<br>'; // 12.34: Result - 12.34USD 



The result of the call (number, currency) also depends on the locale, in particular, the currency symbol is taken from it. So, if you do not yet have a translation for a string using currency, the price will be displayed in dollars, and when the transfer appears, it will be in rubles. Therefore, it is better to use the specialized function yii \ i18n \ Formatter :: asCurrency ().
 $price = 123456; echo \Yii::t('app', 'Price: {price, number, currency}', ['price' => $price]); // Price: $123,456.00 //  'Price: {price, number, currency}' => ': {price, number, currency}' // : 123 456,00 . $price = Yii::$app->formatter->asCurrency(123456, 'GBP'); echo \Yii::t('app', 'Price: {price}', ['price' => $price]); // Price: 123 456,00 £ 


By the way, the locale can be specified in this form: ru-RU @ currency = GBP. Read more in the ICU User Guide .
 $price = 123456; echo Yii::t('app', 'Price: {0, number, currency}', $price, 'ru-RU@currency=GBP'); //  (messages/ru-RU@currency=GBP/app.php) 'Price: {0, number, currency}' => ': {0, number, currency}' // : 123 456,00 £ 


date


time


Documentation Used to display the date and time. You can set the style short, medium, long, full, or specify the pattern manually. When using a pattern, it does not matter what to indicate - date or time. The format is quite simple, a description of all characters can be found in the documentation or in the Unicode standard . Different number of repetitions of a symbol has a different meaning. The formatting takes into account the day on which the week starts in the current locale - Sunday or Monday.
 $d = strtotime('2015-04-18 11:30:16'); echo Yii::t('app', 'Date: {d, date, short} | {d, date, medium} | {d, date, long} | {d, date, full}', ['d' => $d]); echo '<br>'; // Date: 4/18/15 | Apr 18, 2015 | April 18, 2015 | Saturday, April 18, 2015 // : 18.04.15 | 18 . 2015 . | 18  2015 . | , 18  2015 . echo Yii::t('app', 'Time: {d, time, short} | {d, time, medium} | {d, time, long} | {d, time, full}', ['d' => $d]); echo '<br>'; // Time: 11:30 AM | 11:30:16 AM | 11:30:16 AM GMT | 11:30:16 AM GMT // : 11:30 | 11:30:16 | 11:30:16 GMT | 11:30:16 GMT // y, M, d, H, m, s - , , , , ,  echo Yii::t('app', 'Date: {d, date, yyyy-MM-dd HH:mm:ss}', ['d' => $d]); echo '<br>'; // Date: 2015-04-18 11:30:16 // E, e, S, a -  ,   , , AM/PM echo Yii::t('app', "Date: {d, date, d MMMM yyyy; EEEE; e 'day of week'; HH-mm-ss.SSS'ms'; a}", ['d' => $d + 0.100]); echo '<br>'; // Date: 18 April 2015; Saturday; 7 day of week; 11-30-16.100ms; AM // : 18  2015; ; 6  ; 11-30-16.100;   


spellout


Write the number of words. You can use, for example, in some reporting forms, where you need to write the amount in words.
 echo Yii::t('app', '{n, number} is spelled as {n, spellout}', ['n' => 42]); // 42 is spelled as forty-two //  '{n, number} is spelled as {n, spellout}' => '{n, number}   {n, spellout}' // 42     


For Russian, the default is masculine. To withdraw in the female (twenty-one penny), you need to add the name of the set of rules. There should be no space before the '%' sign, otherwise it does not work.
 echo Yii::t('app', '{value, spellout}', ['value' => 21]); // twenty-one //  '{value, spellout}' => '{value, spellout,%spellout-cardinal-feminine}' //   

This method I found on stackoverflow . There is a link to the documentation of the Saxon system , this is the only place where there is a more or less distinct description.

What rules the formatter understands for a particular locale, you can find out like this:
 $formatter = new \NumberFormatter('ru-RU', \NumberFormatter::SPELLOUT); echo $formatter->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS); // %spellout-numbering-year;%spellout-numbering;%spellout-cardinal-masculine;%spellout-cardinal-neuter;%spellout-cardinal-feminine; 

For completeness, it would be nice if the function yii \ i18n \ Formatter :: asSpellout () had the same signature as asInteger (). Now it takes 1 parameter. Then it would be possible to write like this:
 echo Yii::$app->formatter->asSpellout(21, [], [\NumberFormatter::DEFAULT_RULESET => "%spellout-cardinal-feminine"]); 

The types ordinal and duration also support the third parameter. The names of the default rule sets can be found in the ICU project sources (the function RuleBasedNumberFormat :: initDefaultRuleSet ()): spellout - '% spellout-numbering', ordinal - '% digits-ordinal', duration - '% duration'.


ordinal


Displays the sequence number. Analogue selectordinal without the ability to specify options. In Russian, displays only the point after the number.
 echo Yii::t('app', 'You are the {n, ordinal} visitor here!', ['n' => 42]); // You are the 42nd visitor here! //  42.  ! 


duration


The parameter is treated as the number of seconds and is displayed as the duration of something in the 'h: m: s' format. For the Russian language does not work, the value is displayed as is in the integer format.
 echo Yii::t('app', 'You are here for {n, duration} already!', ['n' => 123]); // You are here for 47 sec. already! // You are here for 2:03 already! //     123! 



Behavior features


For the intl extension, you can set whether or not to issue error warnings. The error level is set in php.ini, parameter intl.error_level. If you remove it, warnings do not occur; Messages are not formatted and displayed as is.

If the intl extension is not installed, its operation is emulated, but the possibilities of emulation are limited. The select type is fully supported, for the plural type, only the one and other forms are supported (as for the English language), for the number type, only arguments of the integer type. The remaining types are not supported.

If the format is incorrect, the message “MessageFormatter :: __ construct (): msgfmt_create: message formatter creation failed” appears when the extension is used. In emulation mode it works without errors, the message is displayed as is.

Syntax characters found in the text must be enclosed in apostrophes. The double apostrophe is replaced by a single. A single apostrophe is displayed as it is, but can lead to a message parsing error.
 $count = 3; echo Yii::t('app', "Example of string with ''syntax characters'': '{' '}' '{test}' {count, plural, other{''count'' value is # '#{}'}}", ['count' => $count]); // Example of string with 'syntax characters': { } {test} 'count' value is 3 #{} echo Yii::t('app', "Example of string with ''syntax characters'': ''{' '}' '{test}' {count, plural, other{''count'' value is # '#{}'}}", ['count' => $count]); // MessageFormatter::__construct(): msgfmt_create: message formatter creation failed 


If variables are not transferred to Yii :: t (), or are transmitted simply in curly brackets without formatting rules, then intl will not be accessed. The message will be displayed as is, with double apostrophes, fractional values ​​will be displayed in the system locale. The I18N :: format () function has a regular expression that checks for rules.
 $count = 3.2; echo Yii::t('app', "Example of string with ''syntax characters'': {count}", ['count' => $count]); //  "Example of string with ''syntax characters'': {count}" => "   ''syntax characters'': {count}" //    ''syntax characters'': 3.2 


But it looks like a bug in the intl extension. In some cases, the same variable cannot be used 2 times in the same message, the warning message “MessageFormatter :: format (): Inconsistent types declared for an argument” appears. For example, it cannot be displayed if the type is not specified in one of the options, or as select and plural, but can be output as number and plural. In emulation mode, everything works correctly.
 echo Yii::t('app', '{value} {value, plural, other{test}}', ['value' => 1]); // MessageFormatter::format(): Inconsistent types declared for an argument echo Yii::t('app', '{value, select, other{test}} {value, plural, other{test}}', ['value' => 1]); // MessageFormatter::format(): Inconsistent types declared for an argument echo Yii::t('app', '{value, number} {value, plural, other{test}}', ['value' => 1]); // 1 test 

And even more.Different variables with similar names give the same error. But if the names differ more strongly, the error disappears.
 echo Yii::t('app', '{valueA} {valueB, plural, other{test}}', [ 'valueA' => 1, 'valueB' => 2, ]); // MessageFormatter::format(): Inconsistent types declared for an argument echo Yii::t('app', '{valueA} {valueB1, plural, other{test}}', [ 'valueA' => 1, 'valueB1' => 2, ]); // 1 test 

This warning can be found in the source code of the intl project in the msgformat_helpers.cpp file . A little higher is the comment “We found a different type for the same arg!”, Which clearly does not correspond to the last example.

Small addition
, intl , - , - :
 public static function numberForWord($number) { $number = $number % 100; return ($number < 20 ? $number : $number % 10); } //  $yearNumber = 22; echo Yii::t('app', '{yearNumber}{yearNumberForWord, plural, =1{st} =2{nd} =3{rd} other{th}} year', [ 'yearNumber' => $yearNumber, // 1, 2, 11, 22 'yearNumberForWord' => PluralHelper::numberForWord($yearNumber), // 1, 2, 11, 2 ]); //  '{yearNumber}{yearNumberForWord, plural, =1{st} =2{nd} =3{rd} other{th}} year' => '{yearNumber}{yearNumberForWord, plural, =1{} =2{} =3{} =6{} =7{} =8{} other{}} ', // 22nd year // 22  


The article turned out to be quite large, but I decided that it would be better to let all the basic information on this topic be collected in one place.

Source: https://habr.com/ru/post/264009/


All Articles