messageformat_test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. // Copyright 2010 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.i18n.MessageFormatTest');
  15. goog.setTestOnly('goog.i18n.MessageFormatTest');
  16. goog.require('goog.i18n.MessageFormat');
  17. goog.require('goog.i18n.NumberFormatSymbols_hr');
  18. goog.require('goog.i18n.pluralRules');
  19. goog.require('goog.testing.PropertyReplacer');
  20. goog.require('goog.testing.jsunit');
  21. // Testing stubs that autoreset after each test run.
  22. var stubs = new goog.testing.PropertyReplacer();
  23. function tearDown() {
  24. stubs.reset();
  25. }
  26. function testEmptyPattern() {
  27. var fmt = new goog.i18n.MessageFormat('');
  28. assertEquals('', fmt.format({}));
  29. }
  30. function testMissingLeftCurlyBrace() {
  31. var err = assertThrows(function() {
  32. var fmt = new goog.i18n.MessageFormat('\'\'{}}');
  33. fmt.format({});
  34. });
  35. assertEquals('Assertion failed: No matching { for }.', err.message);
  36. }
  37. function testTooManyLeftCurlyBraces() {
  38. var err = assertThrows(function() {
  39. var fmt = new goog.i18n.MessageFormat('{} {');
  40. fmt.format({});
  41. });
  42. assertEquals(
  43. 'Assertion failed: There are mismatched { or } in the pattern.',
  44. err.message);
  45. }
  46. function testSimpleReplacement() {
  47. var fmt = new goog.i18n.MessageFormat('New York in {SEASON} is nice.');
  48. assertEquals(
  49. 'New York in the Summer is nice.', fmt.format({'SEASON': 'the Summer'}));
  50. }
  51. function testSimpleSelect() {
  52. var fmt = new goog.i18n.MessageFormat(
  53. '{GENDER, select,' +
  54. 'male {His} ' +
  55. 'female {Her} ' +
  56. 'other {Its}}' +
  57. ' bicycle is {GENDER, select, male {blue} female {red} other {green}}.');
  58. assertEquals('His bicycle is blue.', fmt.format({'GENDER': 'male'}));
  59. assertEquals('Her bicycle is red.', fmt.format({'GENDER': 'female'}));
  60. assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'other'}));
  61. assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'whatever'}));
  62. }
  63. function testSimplePlural() {
  64. var fmt = new goog.i18n.MessageFormat(
  65. 'I see {NUM_PEOPLE, plural, offset:1 ' +
  66. '=0 {no one at all in {PLACE}.} ' +
  67. '=1 {{PERSON} in {PLACE}.} ' +
  68. 'one {{PERSON} and one other person in {PLACE}.} ' +
  69. 'other {{PERSON} and # other people in {PLACE}.}}');
  70. assertEquals(
  71. 'I see no one at all in Belgrade.',
  72. fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'}));
  73. assertEquals(
  74. 'I see Markus in Berlin.',
  75. fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'}));
  76. assertEquals(
  77. 'I see Mark and one other person in Athens.',
  78. fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'}));
  79. assertEquals(
  80. 'I see Cibu and 99 other people in the cubes.',
  81. fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
  82. }
  83. function testSimplePluralNoOffset() {
  84. var fmt = new goog.i18n.MessageFormat(
  85. 'I see {NUM_PEOPLE, plural, ' +
  86. '=0 {no one at all} ' +
  87. '=1 {{PERSON}} ' +
  88. 'one {{PERSON} and one other person} ' +
  89. 'other {{PERSON} and # other people}} in {PLACE}.');
  90. assertEquals(
  91. 'I see no one at all in Belgrade.',
  92. fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'}));
  93. assertEquals(
  94. 'I see Markus in Berlin.',
  95. fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'}));
  96. assertEquals(
  97. 'I see Mark and 2 other people in Athens.',
  98. fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'}));
  99. assertEquals(
  100. 'I see Cibu and 100 other people in the cubes.',
  101. fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
  102. }
  103. function testSelectNestedInPlural() {
  104. var fmt = new goog.i18n.MessageFormat(
  105. '{CIRCLES, plural, ' +
  106. 'one {{GENDER, select, ' +
  107. ' female {{WHO} added you to her circle} ' +
  108. ' other {{WHO} added you to his circle}}} ' +
  109. 'other {{GENDER, select, ' +
  110. ' female {{WHO} added you to her # circles} ' +
  111. ' other {{WHO} added you to his # circles}}}}');
  112. assertEquals(
  113. 'Jelena added you to her circle',
  114. fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 1}));
  115. assertEquals(
  116. 'Milan added you to his 1,234 circles',
  117. fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1234}));
  118. }
  119. function testPluralNestedInSelect() {
  120. // Added offset just for testing purposes. It doesn't make sense
  121. // to have it otherwise.
  122. var fmt = new goog.i18n.MessageFormat(
  123. '{GENDER, select, ' +
  124. 'female {{NUM_GROUPS, plural, ' +
  125. ' one {{WHO} added you to her group} ' +
  126. ' other {{WHO} added you to her # groups}}} ' +
  127. 'other {{NUM_GROUPS, plural, offset:1' +
  128. ' one {{WHO} added you to his group} ' +
  129. ' other {{WHO} added you to his # groups}}}}');
  130. assertEquals(
  131. 'Jelena added you to her group',
  132. fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'NUM_GROUPS': 1}));
  133. assertEquals(
  134. 'Milan added you to his 1,233 groups',
  135. fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'NUM_GROUPS': 1234}));
  136. }
  137. function testLiteralOpenCurlyBrace() {
  138. var fmt = new goog.i18n.MessageFormat(
  139. "Anna's house" + " has '{0} and # in the roof' and {NUM_COWS} cows.");
  140. assertEquals(
  141. "Anna's house has {0} and # in the roof and 5 cows.",
  142. fmt.format({'NUM_COWS': '5'}));
  143. }
  144. function testLiteralClosedCurlyBrace() {
  145. var fmt = new goog.i18n.MessageFormat(
  146. "Anna's house" + " has '{'0'} and # in the roof' and {NUM_COWS} cows.");
  147. assertEquals(
  148. "Anna's house has {0} and # in the roof and 5 cows.",
  149. fmt.format({'NUM_COWS': '5'}));
  150. // Regression for: b/34764827
  151. assertEquals(
  152. 'Anna\'s house has {0} and # in the roof and 8 cows.',
  153. fmt.format({'NUM_COWS': '8'}));
  154. }
  155. function testLiteralPoundSign() {
  156. var fmt = new goog.i18n.MessageFormat(
  157. "Anna's house" + " has '{0}' and '# in the roof' and {NUM_COWS} cows.");
  158. assertEquals(
  159. "Anna's house has {0} and # in the roof and 5 cows.",
  160. fmt.format({'NUM_COWS': '5'}));
  161. // Regression for: b/34764827
  162. assertEquals(
  163. 'Anna\'s house has {0} and # in the roof and 10 cows.',
  164. fmt.format({'NUM_COWS': '10'}));
  165. }
  166. function testNoLiteralsForSingleQuotes() {
  167. var fmt =
  168. new goog.i18n.MessageFormat("Anna's house" + " 'has {NUM_COWS} cows'.");
  169. assertEquals("Anna's house 'has 5 cows'.", fmt.format({'NUM_COWS': '5'}));
  170. }
  171. function testConsecutiveSingleQuotesAreReplacedWithOneSingleQuote() {
  172. var fmt = new goog.i18n.MessageFormat("Anna''s house a'{''''b'");
  173. assertEquals("Anna's house a{''b", fmt.format({}));
  174. }
  175. function testConsecutiveSingleQuotesBeforeSpecialCharDontCreateLiteral() {
  176. var fmt = new goog.i18n.MessageFormat("a''{NUM_COWS}'b");
  177. assertEquals("a'5'b", fmt.format({'NUM_COWS': '5'}));
  178. }
  179. function testSerbianSimpleSelect() {
  180. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);
  181. var fmt = new goog.i18n.MessageFormat(
  182. '{GENDER, select, ' +
  183. 'female {Njen} other {Njegov}} bicikl je ' +
  184. '{GENDER, select, female {crven} other {plav}}.');
  185. assertEquals('Njegov bicikl je plav.', fmt.format({'GENDER': 'male'}));
  186. assertEquals('Njen bicikl je crven.', fmt.format({'GENDER': 'female'}));
  187. }
  188. function testSerbianSimplePlural() {
  189. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);
  190. var fmt = new goog.i18n.MessageFormat(
  191. 'Ja {NUM_PEOPLE, plural, offset:1 ' +
  192. '=0 {ne vidim nikoga} ' +
  193. '=1 {vidim {PERSON}} ' +
  194. 'one {vidim {PERSON} i jos # osobu} ' +
  195. 'few {vidim {PERSON} i jos # osobe} ' +
  196. 'many {vidim {PERSON} i jos # osoba} ' +
  197. 'other {{PERSON} i jos # osoba}} ' +
  198. 'u {PLACE}.');
  199. assertEquals(
  200. 'Ja ne vidim nikoga u Beogradu.',
  201. fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'}));
  202. assertEquals(
  203. 'Ja vidim Markusa u Berlinu.',
  204. fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'}));
  205. assertEquals(
  206. 'Ja vidim Marka i jos 1 osobu u Atini.',
  207. fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Marka', 'PLACE': 'Atini'}));
  208. assertEquals(
  209. 'Ja vidim Petra i jos 3 osobe u muzeju.',
  210. fmt.format({'NUM_PEOPLE': 4, 'PERSON': 'Petra', 'PLACE': 'muzeju'}));
  211. assertEquals(
  212. 'Ja vidim Cibua i jos 99 osoba u bazenu.',
  213. fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'}));
  214. }
  215. function testSerbianSimplePluralNoOffset() {
  216. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);
  217. var fmt = new goog.i18n.MessageFormat(
  218. 'Ja {NUM_PEOPLE, plural, ' +
  219. '=0 {ne vidim nikoga} ' +
  220. '=1 {vidim {PERSON}} ' +
  221. 'one {vidim {PERSON} i jos # osobu} ' +
  222. 'few {vidim {PERSON} i jos # osobe} ' +
  223. 'many {vidim {PERSON} i jos # osoba} ' +
  224. 'other {{PERSON} i jos # osoba}} ' +
  225. 'u {PLACE}.');
  226. assertEquals(
  227. 'Ja ne vidim nikoga u Beogradu.',
  228. fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'}));
  229. assertEquals(
  230. 'Ja vidim Markusa u Berlinu.',
  231. fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'}));
  232. assertEquals(
  233. 'Ja vidim Marka i jos 21 osobu u Atini.',
  234. fmt.format({'NUM_PEOPLE': 21, 'PERSON': 'Marka', 'PLACE': 'Atini'}));
  235. assertEquals(
  236. 'Ja vidim Petra i jos 3 osobe u muzeju.',
  237. fmt.format({'NUM_PEOPLE': 3, 'PERSON': 'Petra', 'PLACE': 'muzeju'}));
  238. assertEquals(
  239. 'Ja vidim Cibua i jos 100 osoba u bazenu.',
  240. fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'}));
  241. }
  242. function testSerbianSelectNestedInPlural() {
  243. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);
  244. stubs.set(goog.i18n, 'NumberFormatSymbols', goog.i18n.NumberFormatSymbols_hr);
  245. var fmt = new goog.i18n.MessageFormat(
  246. '{CIRCLES, plural, ' +
  247. 'one {{GENDER, select, ' +
  248. ' female {{WHO} vas je dodala u njen # kruzok} ' +
  249. ' other {{WHO} vas je dodao u njegov # kruzok}}} ' +
  250. 'few {{GENDER, select, ' +
  251. ' female {{WHO} vas je dodala u njena # kruzoka} ' +
  252. ' other {{WHO} vas je dodao u njegova # kruzoka}}} ' +
  253. 'many {{GENDER, select, ' +
  254. ' female {{WHO} vas je dodala u njenih # kruzoka} ' +
  255. ' other {{WHO} vas je dodao u njegovih # kruzoka}}} ' +
  256. 'other {{GENDER, select, ' +
  257. ' female {{WHO} vas je dodala u njenih # kruzoka} ' +
  258. ' other {{WHO} vas je dodao u njegovih # kruzoka}}}}');
  259. assertEquals(
  260. 'Jelena vas je dodala u njen 21 kruzok',
  261. fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 21}));
  262. assertEquals(
  263. 'Jelena vas je dodala u njena 3 kruzoka',
  264. fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 3}));
  265. assertEquals(
  266. 'Jelena vas je dodala u njenih 5 kruzoka',
  267. fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 5}));
  268. assertEquals(
  269. 'Milan vas je dodao u njegovih 1.235 kruzoka',
  270. fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1235}));
  271. }
  272. function testFallbackToOtherOptionInPlurals() {
  273. // Use Arabic plural rules since they have all six cases.
  274. // Only locale and numbers matter, the actual language of the message
  275. // does not.
  276. stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.arSelect_);
  277. var fmt = new goog.i18n.MessageFormat(
  278. '{NUM_MINUTES, plural, ' +
  279. 'other {# minutes}}');
  280. // These numbers exercise all cases for the arabic plural rules.
  281. assertEquals('0 minutes', fmt.format({'NUM_MINUTES': 0}));
  282. assertEquals('1 minutes', fmt.format({'NUM_MINUTES': 1}));
  283. assertEquals('2 minutes', fmt.format({'NUM_MINUTES': 2}));
  284. assertEquals('3 minutes', fmt.format({'NUM_MINUTES': 3}));
  285. assertEquals('11 minutes', fmt.format({'NUM_MINUTES': 11}));
  286. assertEquals('1.5 minutes', fmt.format({'NUM_MINUTES': 1.5}));
  287. }
  288. function testPoundShowsNumberMinusOffsetInAllCases() {
  289. var fmt = new goog.i18n.MessageFormat(
  290. '{SOME_NUM, plural, offset:1 ' +
  291. '=0 {#} =1 {#} =2 {#}one {#} other {#}}');
  292. assertEquals('-1', fmt.format({'SOME_NUM': '0'}));
  293. assertEquals('0', fmt.format({'SOME_NUM': '1'}));
  294. assertEquals('1', fmt.format({'SOME_NUM': '2'}));
  295. assertEquals('20', fmt.format({'SOME_NUM': '21'}));
  296. }
  297. function testSpecialCharactersInParamaterDontChangeFormat() {
  298. var fmt = new goog.i18n.MessageFormat(
  299. '{SOME_NUM, plural,' +
  300. 'other {# {GROUP}}}');
  301. // Test pound sign.
  302. assertEquals(
  303. '10 group#1', fmt.format({'SOME_NUM': '10', 'GROUP': 'group#1'}));
  304. // Test other special characters in parameters, like { and }.
  305. assertEquals('10 } {', fmt.format({'SOME_NUM': '10', 'GROUP': '} {'}));
  306. }
  307. function testMissingOrInvalidPluralParameter() {
  308. var fmt = new goog.i18n.MessageFormat(
  309. '{SOME_NUM, plural,' +
  310. 'other {result}}');
  311. // Key name doesn't match A != SOME_NUM.
  312. assertEquals(
  313. 'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'}));
  314. // Value is not a number.
  315. assertEquals(
  316. 'Undefined or invalid parameter - SOME_NUM',
  317. fmt.format({'SOME_NUM': 'Value'}));
  318. }
  319. function testMissingSelectParameter() {
  320. var fmt = new goog.i18n.MessageFormat(
  321. '{GENDER, select,' +
  322. 'other {result}}');
  323. // Key name doesn't match A != GENDER.
  324. assertEquals('Undefined parameter - GENDER', fmt.format({A: 'female'}));
  325. }
  326. function testMissingSimplePlaceholder() {
  327. var fmt = new goog.i18n.MessageFormat('{result}');
  328. // Key name doesn't match A != result.
  329. assertEquals('Undefined parameter - result', fmt.format({A: 'none'}));
  330. }
  331. function testPluralWithIgnorePound() {
  332. var fmt = new goog.i18n.MessageFormat(
  333. '{SOME_NUM, plural,' +
  334. 'other {# {GROUP}}}');
  335. // Test pound sign.
  336. assertEquals(
  337. '# group#1',
  338. fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': 'group#1'}));
  339. // Test other special characters in parameters, like { and }.
  340. assertEquals(
  341. '# } {', fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': '} {'}));
  342. }
  343. function testSimplePluralWithIgnorePound() {
  344. var fmt = new goog.i18n.MessageFormat(
  345. 'I see {NUM_PEOPLE, plural, offset:1 ' +
  346. '=0 {no one at all in {PLACE}.} ' +
  347. '=1 {{PERSON} in {PLACE}.} ' +
  348. 'one {{PERSON} and one other person in {PLACE}.} ' +
  349. 'other {{PERSON} and # other people in {PLACE}.}}');
  350. assertEquals(
  351. 'I see Cibu and # other people in the cubes.',
  352. fmt.formatIgnoringPound(
  353. {'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
  354. }
  355. function testSimpleOrdinal() {
  356. var fmt = new goog.i18n.MessageFormat(
  357. '{NUM_FLOOR, selectordinal, ' +
  358. 'one {Take the elevator to the #st floor.}' +
  359. 'two {Take the elevator to the #nd floor.}' +
  360. 'few {Take the elevator to the #rd floor.}' +
  361. 'other {Take the elevator to the #th floor.}}');
  362. assertEquals(
  363. 'Take the elevator to the 1st floor.', fmt.format({'NUM_FLOOR': 1}));
  364. assertEquals(
  365. 'Take the elevator to the 2nd floor.', fmt.format({'NUM_FLOOR': 2}));
  366. assertEquals(
  367. 'Take the elevator to the 3rd floor.', fmt.format({'NUM_FLOOR': 3}));
  368. assertEquals(
  369. 'Take the elevator to the 4th floor.', fmt.format({'NUM_FLOOR': 4}));
  370. assertEquals(
  371. 'Take the elevator to the 23rd floor.', fmt.format({'NUM_FLOOR': 23}));
  372. // Esoteric example.
  373. assertEquals(
  374. 'Take the elevator to the 0th floor.', fmt.format({'NUM_FLOOR': 0}));
  375. }
  376. function testOrdinalWithNegativeValue() {
  377. var fmt = new goog.i18n.MessageFormat(
  378. '{NUM_FLOOR, selectordinal, ' +
  379. 'one {Take the elevator to the #st floor.}' +
  380. 'two {Take the elevator to the #nd floor.}' +
  381. 'few {Take the elevator to the #rd floor.}' +
  382. 'other {Take the elevator to the #th floor.}}');
  383. try {
  384. fmt.format({'NUM_FLOOR': -2});
  385. } catch (e) {
  386. assertEquals(
  387. 'Assertion failed: Argument index smaller than offset.', e.message);
  388. return;
  389. }
  390. fail('Expected an error to be thrown');
  391. }
  392. function testSimpleOrdinalWithIgnorePound() {
  393. var fmt = new goog.i18n.MessageFormat(
  394. '{NUM_FLOOR, selectordinal, ' +
  395. 'one {Take the elevator to the #st floor.}' +
  396. 'two {Take the elevator to the #nd floor.}' +
  397. 'few {Take the elevator to the #rd floor.}' +
  398. 'other {Take the elevator to the #th floor.}}');
  399. assertEquals(
  400. 'Take the elevator to the #th floor.',
  401. fmt.formatIgnoringPound({'NUM_FLOOR': 100}));
  402. }
  403. function testMissingOrInvalidOrdinalParameter() {
  404. var fmt = new goog.i18n.MessageFormat(
  405. '{SOME_NUM, selectordinal,' +
  406. 'other {result}}');
  407. // Key name doesn't match A != SOME_NUM.
  408. assertEquals(
  409. 'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'}));
  410. // Value is not a number.
  411. assertEquals(
  412. 'Undefined or invalid parameter - SOME_NUM',
  413. fmt.format({'SOME_NUM': 'Value'}));
  414. }