// Copyright 2010 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.i18n.MessageFormatTest'); goog.setTestOnly('goog.i18n.MessageFormatTest'); goog.require('goog.i18n.MessageFormat'); goog.require('goog.i18n.NumberFormatSymbols_hr'); goog.require('goog.i18n.pluralRules'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); // Testing stubs that autoreset after each test run. var stubs = new goog.testing.PropertyReplacer(); function tearDown() { stubs.reset(); } function testEmptyPattern() { var fmt = new goog.i18n.MessageFormat(''); assertEquals('', fmt.format({})); } function testMissingLeftCurlyBrace() { var err = assertThrows(function() { var fmt = new goog.i18n.MessageFormat('\'\'{}}'); fmt.format({}); }); assertEquals('Assertion failed: No matching { for }.', err.message); } function testTooManyLeftCurlyBraces() { var err = assertThrows(function() { var fmt = new goog.i18n.MessageFormat('{} {'); fmt.format({}); }); assertEquals( 'Assertion failed: There are mismatched { or } in the pattern.', err.message); } function testSimpleReplacement() { var fmt = new goog.i18n.MessageFormat('New York in {SEASON} is nice.'); assertEquals( 'New York in the Summer is nice.', fmt.format({'SEASON': 'the Summer'})); } function testSimpleSelect() { var fmt = new goog.i18n.MessageFormat( '{GENDER, select,' + 'male {His} ' + 'female {Her} ' + 'other {Its}}' + ' bicycle is {GENDER, select, male {blue} female {red} other {green}}.'); assertEquals('His bicycle is blue.', fmt.format({'GENDER': 'male'})); assertEquals('Her bicycle is red.', fmt.format({'GENDER': 'female'})); assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'other'})); assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'whatever'})); } function testSimplePlural() { var fmt = new goog.i18n.MessageFormat( 'I see {NUM_PEOPLE, plural, offset:1 ' + '=0 {no one at all in {PLACE}.} ' + '=1 {{PERSON} in {PLACE}.} ' + 'one {{PERSON} and one other person in {PLACE}.} ' + 'other {{PERSON} and # other people in {PLACE}.}}'); assertEquals( 'I see no one at all in Belgrade.', fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'})); assertEquals( 'I see Markus in Berlin.', fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'})); assertEquals( 'I see Mark and one other person in Athens.', fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'})); assertEquals( 'I see Cibu and 99 other people in the cubes.', fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'})); } function testSimplePluralNoOffset() { var fmt = new goog.i18n.MessageFormat( 'I see {NUM_PEOPLE, plural, ' + '=0 {no one at all} ' + '=1 {{PERSON}} ' + 'one {{PERSON} and one other person} ' + 'other {{PERSON} and # other people}} in {PLACE}.'); assertEquals( 'I see no one at all in Belgrade.', fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'})); assertEquals( 'I see Markus in Berlin.', fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'})); assertEquals( 'I see Mark and 2 other people in Athens.', fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'})); assertEquals( 'I see Cibu and 100 other people in the cubes.', fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'})); } function testSelectNestedInPlural() { var fmt = new goog.i18n.MessageFormat( '{CIRCLES, plural, ' + 'one {{GENDER, select, ' + ' female {{WHO} added you to her circle} ' + ' other {{WHO} added you to his circle}}} ' + 'other {{GENDER, select, ' + ' female {{WHO} added you to her # circles} ' + ' other {{WHO} added you to his # circles}}}}'); assertEquals( 'Jelena added you to her circle', fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 1})); assertEquals( 'Milan added you to his 1,234 circles', fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1234})); } function testPluralNestedInSelect() { // Added offset just for testing purposes. It doesn't make sense // to have it otherwise. var fmt = new goog.i18n.MessageFormat( '{GENDER, select, ' + 'female {{NUM_GROUPS, plural, ' + ' one {{WHO} added you to her group} ' + ' other {{WHO} added you to her # groups}}} ' + 'other {{NUM_GROUPS, plural, offset:1' + ' one {{WHO} added you to his group} ' + ' other {{WHO} added you to his # groups}}}}'); assertEquals( 'Jelena added you to her group', fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'NUM_GROUPS': 1})); assertEquals( 'Milan added you to his 1,233 groups', fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'NUM_GROUPS': 1234})); } function testLiteralOpenCurlyBrace() { var fmt = new goog.i18n.MessageFormat( "Anna's house" + " has '{0} and # in the roof' and {NUM_COWS} cows."); assertEquals( "Anna's house has {0} and # in the roof and 5 cows.", fmt.format({'NUM_COWS': '5'})); } function testLiteralClosedCurlyBrace() { var fmt = new goog.i18n.MessageFormat( "Anna's house" + " has '{'0'} and # in the roof' and {NUM_COWS} cows."); assertEquals( "Anna's house has {0} and # in the roof and 5 cows.", fmt.format({'NUM_COWS': '5'})); // Regression for: b/34764827 assertEquals( 'Anna\'s house has {0} and # in the roof and 8 cows.', fmt.format({'NUM_COWS': '8'})); } function testLiteralPoundSign() { var fmt = new goog.i18n.MessageFormat( "Anna's house" + " has '{0}' and '# in the roof' and {NUM_COWS} cows."); assertEquals( "Anna's house has {0} and # in the roof and 5 cows.", fmt.format({'NUM_COWS': '5'})); // Regression for: b/34764827 assertEquals( 'Anna\'s house has {0} and # in the roof and 10 cows.', fmt.format({'NUM_COWS': '10'})); } function testNoLiteralsForSingleQuotes() { var fmt = new goog.i18n.MessageFormat("Anna's house" + " 'has {NUM_COWS} cows'."); assertEquals("Anna's house 'has 5 cows'.", fmt.format({'NUM_COWS': '5'})); } function testConsecutiveSingleQuotesAreReplacedWithOneSingleQuote() { var fmt = new goog.i18n.MessageFormat("Anna''s house a'{''''b'"); assertEquals("Anna's house a{''b", fmt.format({})); } function testConsecutiveSingleQuotesBeforeSpecialCharDontCreateLiteral() { var fmt = new goog.i18n.MessageFormat("a''{NUM_COWS}'b"); assertEquals("a'5'b", fmt.format({'NUM_COWS': '5'})); } function testSerbianSimpleSelect() { stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); var fmt = new goog.i18n.MessageFormat( '{GENDER, select, ' + 'female {Njen} other {Njegov}} bicikl je ' + '{GENDER, select, female {crven} other {plav}}.'); assertEquals('Njegov bicikl je plav.', fmt.format({'GENDER': 'male'})); assertEquals('Njen bicikl je crven.', fmt.format({'GENDER': 'female'})); } function testSerbianSimplePlural() { stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); var fmt = new goog.i18n.MessageFormat( 'Ja {NUM_PEOPLE, plural, offset:1 ' + '=0 {ne vidim nikoga} ' + '=1 {vidim {PERSON}} ' + 'one {vidim {PERSON} i jos # osobu} ' + 'few {vidim {PERSON} i jos # osobe} ' + 'many {vidim {PERSON} i jos # osoba} ' + 'other {{PERSON} i jos # osoba}} ' + 'u {PLACE}.'); assertEquals( 'Ja ne vidim nikoga u Beogradu.', fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'})); assertEquals( 'Ja vidim Markusa u Berlinu.', fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'})); assertEquals( 'Ja vidim Marka i jos 1 osobu u Atini.', fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Marka', 'PLACE': 'Atini'})); assertEquals( 'Ja vidim Petra i jos 3 osobe u muzeju.', fmt.format({'NUM_PEOPLE': 4, 'PERSON': 'Petra', 'PLACE': 'muzeju'})); assertEquals( 'Ja vidim Cibua i jos 99 osoba u bazenu.', fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'})); } function testSerbianSimplePluralNoOffset() { stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); var fmt = new goog.i18n.MessageFormat( 'Ja {NUM_PEOPLE, plural, ' + '=0 {ne vidim nikoga} ' + '=1 {vidim {PERSON}} ' + 'one {vidim {PERSON} i jos # osobu} ' + 'few {vidim {PERSON} i jos # osobe} ' + 'many {vidim {PERSON} i jos # osoba} ' + 'other {{PERSON} i jos # osoba}} ' + 'u {PLACE}.'); assertEquals( 'Ja ne vidim nikoga u Beogradu.', fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'})); assertEquals( 'Ja vidim Markusa u Berlinu.', fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'})); assertEquals( 'Ja vidim Marka i jos 21 osobu u Atini.', fmt.format({'NUM_PEOPLE': 21, 'PERSON': 'Marka', 'PLACE': 'Atini'})); assertEquals( 'Ja vidim Petra i jos 3 osobe u muzeju.', fmt.format({'NUM_PEOPLE': 3, 'PERSON': 'Petra', 'PLACE': 'muzeju'})); assertEquals( 'Ja vidim Cibua i jos 100 osoba u bazenu.', fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'})); } function testSerbianSelectNestedInPlural() { stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_); stubs.set(goog.i18n, 'NumberFormatSymbols', goog.i18n.NumberFormatSymbols_hr); var fmt = new goog.i18n.MessageFormat( '{CIRCLES, plural, ' + 'one {{GENDER, select, ' + ' female {{WHO} vas je dodala u njen # kruzok} ' + ' other {{WHO} vas je dodao u njegov # kruzok}}} ' + 'few {{GENDER, select, ' + ' female {{WHO} vas je dodala u njena # kruzoka} ' + ' other {{WHO} vas je dodao u njegova # kruzoka}}} ' + 'many {{GENDER, select, ' + ' female {{WHO} vas je dodala u njenih # kruzoka} ' + ' other {{WHO} vas je dodao u njegovih # kruzoka}}} ' + 'other {{GENDER, select, ' + ' female {{WHO} vas je dodala u njenih # kruzoka} ' + ' other {{WHO} vas je dodao u njegovih # kruzoka}}}}'); assertEquals( 'Jelena vas je dodala u njen 21 kruzok', fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 21})); assertEquals( 'Jelena vas je dodala u njena 3 kruzoka', fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 3})); assertEquals( 'Jelena vas je dodala u njenih 5 kruzoka', fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 5})); assertEquals( 'Milan vas je dodao u njegovih 1.235 kruzoka', fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1235})); } function testFallbackToOtherOptionInPlurals() { // Use Arabic plural rules since they have all six cases. // Only locale and numbers matter, the actual language of the message // does not. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.arSelect_); var fmt = new goog.i18n.MessageFormat( '{NUM_MINUTES, plural, ' + 'other {# minutes}}'); // These numbers exercise all cases for the arabic plural rules. assertEquals('0 minutes', fmt.format({'NUM_MINUTES': 0})); assertEquals('1 minutes', fmt.format({'NUM_MINUTES': 1})); assertEquals('2 minutes', fmt.format({'NUM_MINUTES': 2})); assertEquals('3 minutes', fmt.format({'NUM_MINUTES': 3})); assertEquals('11 minutes', fmt.format({'NUM_MINUTES': 11})); assertEquals('1.5 minutes', fmt.format({'NUM_MINUTES': 1.5})); } function testPoundShowsNumberMinusOffsetInAllCases() { var fmt = new goog.i18n.MessageFormat( '{SOME_NUM, plural, offset:1 ' + '=0 {#} =1 {#} =2 {#}one {#} other {#}}'); assertEquals('-1', fmt.format({'SOME_NUM': '0'})); assertEquals('0', fmt.format({'SOME_NUM': '1'})); assertEquals('1', fmt.format({'SOME_NUM': '2'})); assertEquals('20', fmt.format({'SOME_NUM': '21'})); } function testSpecialCharactersInParamaterDontChangeFormat() { var fmt = new goog.i18n.MessageFormat( '{SOME_NUM, plural,' + 'other {# {GROUP}}}'); // Test pound sign. assertEquals( '10 group#1', fmt.format({'SOME_NUM': '10', 'GROUP': 'group#1'})); // Test other special characters in parameters, like { and }. assertEquals('10 } {', fmt.format({'SOME_NUM': '10', 'GROUP': '} {'})); } function testMissingOrInvalidPluralParameter() { var fmt = new goog.i18n.MessageFormat( '{SOME_NUM, plural,' + 'other {result}}'); // Key name doesn't match A != SOME_NUM. assertEquals( 'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'})); // Value is not a number. assertEquals( 'Undefined or invalid parameter - SOME_NUM', fmt.format({'SOME_NUM': 'Value'})); } function testMissingSelectParameter() { var fmt = new goog.i18n.MessageFormat( '{GENDER, select,' + 'other {result}}'); // Key name doesn't match A != GENDER. assertEquals('Undefined parameter - GENDER', fmt.format({A: 'female'})); } function testMissingSimplePlaceholder() { var fmt = new goog.i18n.MessageFormat('{result}'); // Key name doesn't match A != result. assertEquals('Undefined parameter - result', fmt.format({A: 'none'})); } function testPluralWithIgnorePound() { var fmt = new goog.i18n.MessageFormat( '{SOME_NUM, plural,' + 'other {# {GROUP}}}'); // Test pound sign. assertEquals( '# group#1', fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': 'group#1'})); // Test other special characters in parameters, like { and }. assertEquals( '# } {', fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': '} {'})); } function testSimplePluralWithIgnorePound() { var fmt = new goog.i18n.MessageFormat( 'I see {NUM_PEOPLE, plural, offset:1 ' + '=0 {no one at all in {PLACE}.} ' + '=1 {{PERSON} in {PLACE}.} ' + 'one {{PERSON} and one other person in {PLACE}.} ' + 'other {{PERSON} and # other people in {PLACE}.}}'); assertEquals( 'I see Cibu and # other people in the cubes.', fmt.formatIgnoringPound( {'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'})); } function testSimpleOrdinal() { var fmt = new goog.i18n.MessageFormat( '{NUM_FLOOR, selectordinal, ' + 'one {Take the elevator to the #st floor.}' + 'two {Take the elevator to the #nd floor.}' + 'few {Take the elevator to the #rd floor.}' + 'other {Take the elevator to the #th floor.}}'); assertEquals( 'Take the elevator to the 1st floor.', fmt.format({'NUM_FLOOR': 1})); assertEquals( 'Take the elevator to the 2nd floor.', fmt.format({'NUM_FLOOR': 2})); assertEquals( 'Take the elevator to the 3rd floor.', fmt.format({'NUM_FLOOR': 3})); assertEquals( 'Take the elevator to the 4th floor.', fmt.format({'NUM_FLOOR': 4})); assertEquals( 'Take the elevator to the 23rd floor.', fmt.format({'NUM_FLOOR': 23})); // Esoteric example. assertEquals( 'Take the elevator to the 0th floor.', fmt.format({'NUM_FLOOR': 0})); } function testOrdinalWithNegativeValue() { var fmt = new goog.i18n.MessageFormat( '{NUM_FLOOR, selectordinal, ' + 'one {Take the elevator to the #st floor.}' + 'two {Take the elevator to the #nd floor.}' + 'few {Take the elevator to the #rd floor.}' + 'other {Take the elevator to the #th floor.}}'); try { fmt.format({'NUM_FLOOR': -2}); } catch (e) { assertEquals( 'Assertion failed: Argument index smaller than offset.', e.message); return; } fail('Expected an error to be thrown'); } function testSimpleOrdinalWithIgnorePound() { var fmt = new goog.i18n.MessageFormat( '{NUM_FLOOR, selectordinal, ' + 'one {Take the elevator to the #st floor.}' + 'two {Take the elevator to the #nd floor.}' + 'few {Take the elevator to the #rd floor.}' + 'other {Take the elevator to the #th floor.}}'); assertEquals( 'Take the elevator to the #th floor.', fmt.formatIgnoringPound({'NUM_FLOOR': 100})); } function testMissingOrInvalidOrdinalParameter() { var fmt = new goog.i18n.MessageFormat( '{SOME_NUM, selectordinal,' + 'other {result}}'); // Key name doesn't match A != SOME_NUM. assertEquals( 'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'})); // Value is not a number. assertEquals( 'Undefined or invalid parameter - SOME_NUM', fmt.format({'SOME_NUM': 'Value'})); }