Index: trunk/phase3/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js |
— | — | @@ -0,0 +1,488 @@ |
| 2 | +// This file stores the results from the PHP parser for certain messages and arguments, |
| 3 | +// so we can test the equivalent Javascript libraries. |
| 4 | +// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00 |
| 5 | + |
| 6 | +mediaWiki.messages.set( { |
| 7 | + "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", |
| 8 | + "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", |
| 9 | + "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}", |
| 10 | + "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.", |
| 11 | + "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}", |
| 12 | + "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}", |
| 13 | + "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", |
| 14 | + "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", |
| 15 | + "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2", |
| 16 | + "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}" |
| 17 | +} ); |
| 18 | +var jasmineMsgSpec = [ |
| 19 | + { |
| 20 | + "name": "en undelete_short 0", |
| 21 | + "key": "en_undelete_short", |
| 22 | + "args": [ |
| 23 | + 0 |
| 24 | + ], |
| 25 | + "result": "Undelete 0 edits", |
| 26 | + "lang": "en" |
| 27 | + }, |
| 28 | + { |
| 29 | + "name": "en undelete_short 1", |
| 30 | + "key": "en_undelete_short", |
| 31 | + "args": [ |
| 32 | + 1 |
| 33 | + ], |
| 34 | + "result": "Undelete one edit", |
| 35 | + "lang": "en" |
| 36 | + }, |
| 37 | + { |
| 38 | + "name": "en undelete_short 2", |
| 39 | + "key": "en_undelete_short", |
| 40 | + "args": [ |
| 41 | + 2 |
| 42 | + ], |
| 43 | + "result": "Undelete 2 edits", |
| 44 | + "lang": "en" |
| 45 | + }, |
| 46 | + { |
| 47 | + "name": "en undelete_short 5", |
| 48 | + "key": "en_undelete_short", |
| 49 | + "args": [ |
| 50 | + 5 |
| 51 | + ], |
| 52 | + "result": "Undelete 5 edits", |
| 53 | + "lang": "en" |
| 54 | + }, |
| 55 | + { |
| 56 | + "name": "en undelete_short 21", |
| 57 | + "key": "en_undelete_short", |
| 58 | + "args": [ |
| 59 | + 21 |
| 60 | + ], |
| 61 | + "result": "Undelete 21 edits", |
| 62 | + "lang": "en" |
| 63 | + }, |
| 64 | + { |
| 65 | + "name": "en undelete_short 101", |
| 66 | + "key": "en_undelete_short", |
| 67 | + "args": [ |
| 68 | + 101 |
| 69 | + ], |
| 70 | + "result": "Undelete 101 edits", |
| 71 | + "lang": "en" |
| 72 | + }, |
| 73 | + { |
| 74 | + "name": "en category-subcat-count 0,10", |
| 75 | + "key": "en_category-subcat-count", |
| 76 | + "args": [ |
| 77 | + 0, |
| 78 | + 10 |
| 79 | + ], |
| 80 | + "result": "This category has the following 0 subcategories, out of 10 total.", |
| 81 | + "lang": "en" |
| 82 | + }, |
| 83 | + { |
| 84 | + "name": "en category-subcat-count 1,1", |
| 85 | + "key": "en_category-subcat-count", |
| 86 | + "args": [ |
| 87 | + 1, |
| 88 | + 1 |
| 89 | + ], |
| 90 | + "result": "This category has only the following subcategory.", |
| 91 | + "lang": "en" |
| 92 | + }, |
| 93 | + { |
| 94 | + "name": "en category-subcat-count 1,2", |
| 95 | + "key": "en_category-subcat-count", |
| 96 | + "args": [ |
| 97 | + 1, |
| 98 | + 2 |
| 99 | + ], |
| 100 | + "result": "This category has the following subcategory, out of 2 total.", |
| 101 | + "lang": "en" |
| 102 | + }, |
| 103 | + { |
| 104 | + "name": "en category-subcat-count 3,30", |
| 105 | + "key": "en_category-subcat-count", |
| 106 | + "args": [ |
| 107 | + 3, |
| 108 | + 30 |
| 109 | + ], |
| 110 | + "result": "This category has the following 3 subcategories, out of 30 total.", |
| 111 | + "lang": "en" |
| 112 | + }, |
| 113 | + { |
| 114 | + "name": "fr undelete_short 0", |
| 115 | + "key": "fr_undelete_short", |
| 116 | + "args": [ |
| 117 | + 0 |
| 118 | + ], |
| 119 | + "result": "Restaurer 0 modification", |
| 120 | + "lang": "fr" |
| 121 | + }, |
| 122 | + { |
| 123 | + "name": "fr undelete_short 1", |
| 124 | + "key": "fr_undelete_short", |
| 125 | + "args": [ |
| 126 | + 1 |
| 127 | + ], |
| 128 | + "result": "Restaurer 1 modification", |
| 129 | + "lang": "fr" |
| 130 | + }, |
| 131 | + { |
| 132 | + "name": "fr undelete_short 2", |
| 133 | + "key": "fr_undelete_short", |
| 134 | + "args": [ |
| 135 | + 2 |
| 136 | + ], |
| 137 | + "result": "Restaurer 2 modifications", |
| 138 | + "lang": "fr" |
| 139 | + }, |
| 140 | + { |
| 141 | + "name": "fr undelete_short 5", |
| 142 | + "key": "fr_undelete_short", |
| 143 | + "args": [ |
| 144 | + 5 |
| 145 | + ], |
| 146 | + "result": "Restaurer 5 modifications", |
| 147 | + "lang": "fr" |
| 148 | + }, |
| 149 | + { |
| 150 | + "name": "fr undelete_short 21", |
| 151 | + "key": "fr_undelete_short", |
| 152 | + "args": [ |
| 153 | + 21 |
| 154 | + ], |
| 155 | + "result": "Restaurer 21 modifications", |
| 156 | + "lang": "fr" |
| 157 | + }, |
| 158 | + { |
| 159 | + "name": "fr undelete_short 101", |
| 160 | + "key": "fr_undelete_short", |
| 161 | + "args": [ |
| 162 | + 101 |
| 163 | + ], |
| 164 | + "result": "Restaurer 101 modifications", |
| 165 | + "lang": "fr" |
| 166 | + }, |
| 167 | + { |
| 168 | + "name": "fr category-subcat-count 0,10", |
| 169 | + "key": "fr_category-subcat-count", |
| 170 | + "args": [ |
| 171 | + 0, |
| 172 | + 10 |
| 173 | + ], |
| 174 | + "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.", |
| 175 | + "lang": "fr" |
| 176 | + }, |
| 177 | + { |
| 178 | + "name": "fr category-subcat-count 1,1", |
| 179 | + "key": "fr_category-subcat-count", |
| 180 | + "args": [ |
| 181 | + 1, |
| 182 | + 1 |
| 183 | + ], |
| 184 | + "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.", |
| 185 | + "lang": "fr" |
| 186 | + }, |
| 187 | + { |
| 188 | + "name": "fr category-subcat-count 1,2", |
| 189 | + "key": "fr_category-subcat-count", |
| 190 | + "args": [ |
| 191 | + 1, |
| 192 | + 2 |
| 193 | + ], |
| 194 | + "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.", |
| 195 | + "lang": "fr" |
| 196 | + }, |
| 197 | + { |
| 198 | + "name": "fr category-subcat-count 3,30", |
| 199 | + "key": "fr_category-subcat-count", |
| 200 | + "args": [ |
| 201 | + 3, |
| 202 | + 30 |
| 203 | + ], |
| 204 | + "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.", |
| 205 | + "lang": "fr" |
| 206 | + }, |
| 207 | + { |
| 208 | + "name": "ar undelete_short 0", |
| 209 | + "key": "ar_undelete_short", |
| 210 | + "args": [ |
| 211 | + 0 |
| 212 | + ], |
| 213 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f", |
| 214 | + "lang": "ar" |
| 215 | + }, |
| 216 | + { |
| 217 | + "name": "ar undelete_short 1", |
| 218 | + "key": "ar_undelete_short", |
| 219 | + "args": [ |
| 220 | + 1 |
| 221 | + ], |
| 222 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646", |
| 223 | + "lang": "ar" |
| 224 | + }, |
| 225 | + { |
| 226 | + "name": "ar undelete_short 2", |
| 227 | + "key": "ar_undelete_short", |
| 228 | + "args": [ |
| 229 | + 2 |
| 230 | + ], |
| 231 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a", |
| 232 | + "lang": "ar" |
| 233 | + }, |
| 234 | + { |
| 235 | + "name": "ar undelete_short 5", |
| 236 | + "key": "ar_undelete_short", |
| 237 | + "args": [ |
| 238 | + 5 |
| 239 | + ], |
| 240 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644", |
| 241 | + "lang": "ar" |
| 242 | + }, |
| 243 | + { |
| 244 | + "name": "ar undelete_short 21", |
| 245 | + "key": "ar_undelete_short", |
| 246 | + "args": [ |
| 247 | + 21 |
| 248 | + ], |
| 249 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627", |
| 250 | + "lang": "ar" |
| 251 | + }, |
| 252 | + { |
| 253 | + "name": "ar undelete_short 101", |
| 254 | + "key": "ar_undelete_short", |
| 255 | + "args": [ |
| 256 | + 101 |
| 257 | + ], |
| 258 | + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627", |
| 259 | + "lang": "ar" |
| 260 | + }, |
| 261 | + { |
| 262 | + "name": "ar category-subcat-count 0,10", |
| 263 | + "key": "ar_category-subcat-count", |
| 264 | + "args": [ |
| 265 | + 0, |
| 266 | + 10 |
| 267 | + ], |
| 268 | + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.", |
| 269 | + "lang": "ar" |
| 270 | + }, |
| 271 | + { |
| 272 | + "name": "ar category-subcat-count 1,1", |
| 273 | + "key": "ar_category-subcat-count", |
| 274 | + "args": [ |
| 275 | + 1, |
| 276 | + 1 |
| 277 | + ], |
| 278 | + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.", |
| 279 | + "lang": "ar" |
| 280 | + }, |
| 281 | + { |
| 282 | + "name": "ar category-subcat-count 1,2", |
| 283 | + "key": "ar_category-subcat-count", |
| 284 | + "args": [ |
| 285 | + 1, |
| 286 | + 2 |
| 287 | + ], |
| 288 | + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.", |
| 289 | + "lang": "ar" |
| 290 | + }, |
| 291 | + { |
| 292 | + "name": "ar category-subcat-count 3,30", |
| 293 | + "key": "ar_category-subcat-count", |
| 294 | + "args": [ |
| 295 | + 3, |
| 296 | + 30 |
| 297 | + ], |
| 298 | + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.", |
| 299 | + "lang": "ar" |
| 300 | + }, |
| 301 | + { |
| 302 | + "name": "jp undelete_short 0", |
| 303 | + "key": "jp_undelete_short", |
| 304 | + "args": [ |
| 305 | + 0 |
| 306 | + ], |
| 307 | + "result": "Undelete 0 edits", |
| 308 | + "lang": "jp" |
| 309 | + }, |
| 310 | + { |
| 311 | + "name": "jp undelete_short 1", |
| 312 | + "key": "jp_undelete_short", |
| 313 | + "args": [ |
| 314 | + 1 |
| 315 | + ], |
| 316 | + "result": "Undelete one edit", |
| 317 | + "lang": "jp" |
| 318 | + }, |
| 319 | + { |
| 320 | + "name": "jp undelete_short 2", |
| 321 | + "key": "jp_undelete_short", |
| 322 | + "args": [ |
| 323 | + 2 |
| 324 | + ], |
| 325 | + "result": "Undelete 2 edits", |
| 326 | + "lang": "jp" |
| 327 | + }, |
| 328 | + { |
| 329 | + "name": "jp undelete_short 5", |
| 330 | + "key": "jp_undelete_short", |
| 331 | + "args": [ |
| 332 | + 5 |
| 333 | + ], |
| 334 | + "result": "Undelete 5 edits", |
| 335 | + "lang": "jp" |
| 336 | + }, |
| 337 | + { |
| 338 | + "name": "jp undelete_short 21", |
| 339 | + "key": "jp_undelete_short", |
| 340 | + "args": [ |
| 341 | + 21 |
| 342 | + ], |
| 343 | + "result": "Undelete 21 edits", |
| 344 | + "lang": "jp" |
| 345 | + }, |
| 346 | + { |
| 347 | + "name": "jp undelete_short 101", |
| 348 | + "key": "jp_undelete_short", |
| 349 | + "args": [ |
| 350 | + 101 |
| 351 | + ], |
| 352 | + "result": "Undelete 101 edits", |
| 353 | + "lang": "jp" |
| 354 | + }, |
| 355 | + { |
| 356 | + "name": "jp category-subcat-count 0,10", |
| 357 | + "key": "jp_category-subcat-count", |
| 358 | + "args": [ |
| 359 | + 0, |
| 360 | + 10 |
| 361 | + ], |
| 362 | + "result": "This category has the following 0 subcategories, out of 10 total.", |
| 363 | + "lang": "jp" |
| 364 | + }, |
| 365 | + { |
| 366 | + "name": "jp category-subcat-count 1,1", |
| 367 | + "key": "jp_category-subcat-count", |
| 368 | + "args": [ |
| 369 | + 1, |
| 370 | + 1 |
| 371 | + ], |
| 372 | + "result": "This category has only the following subcategory.", |
| 373 | + "lang": "jp" |
| 374 | + }, |
| 375 | + { |
| 376 | + "name": "jp category-subcat-count 1,2", |
| 377 | + "key": "jp_category-subcat-count", |
| 378 | + "args": [ |
| 379 | + 1, |
| 380 | + 2 |
| 381 | + ], |
| 382 | + "result": "This category has the following subcategory, out of 2 total.", |
| 383 | + "lang": "jp" |
| 384 | + }, |
| 385 | + { |
| 386 | + "name": "jp category-subcat-count 3,30", |
| 387 | + "key": "jp_category-subcat-count", |
| 388 | + "args": [ |
| 389 | + 3, |
| 390 | + 30 |
| 391 | + ], |
| 392 | + "result": "This category has the following 3 subcategories, out of 30 total.", |
| 393 | + "lang": "jp" |
| 394 | + }, |
| 395 | + { |
| 396 | + "name": "zh undelete_short 0", |
| 397 | + "key": "zh_undelete_short", |
| 398 | + "args": [ |
| 399 | + 0 |
| 400 | + ], |
| 401 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2", |
| 402 | + "lang": "zh" |
| 403 | + }, |
| 404 | + { |
| 405 | + "name": "zh undelete_short 1", |
| 406 | + "key": "zh_undelete_short", |
| 407 | + "args": [ |
| 408 | + 1 |
| 409 | + ], |
| 410 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2", |
| 411 | + "lang": "zh" |
| 412 | + }, |
| 413 | + { |
| 414 | + "name": "zh undelete_short 2", |
| 415 | + "key": "zh_undelete_short", |
| 416 | + "args": [ |
| 417 | + 2 |
| 418 | + ], |
| 419 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2", |
| 420 | + "lang": "zh" |
| 421 | + }, |
| 422 | + { |
| 423 | + "name": "zh undelete_short 5", |
| 424 | + "key": "zh_undelete_short", |
| 425 | + "args": [ |
| 426 | + 5 |
| 427 | + ], |
| 428 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2", |
| 429 | + "lang": "zh" |
| 430 | + }, |
| 431 | + { |
| 432 | + "name": "zh undelete_short 21", |
| 433 | + "key": "zh_undelete_short", |
| 434 | + "args": [ |
| 435 | + 21 |
| 436 | + ], |
| 437 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2", |
| 438 | + "lang": "zh" |
| 439 | + }, |
| 440 | + { |
| 441 | + "name": "zh undelete_short 101", |
| 442 | + "key": "zh_undelete_short", |
| 443 | + "args": [ |
| 444 | + 101 |
| 445 | + ], |
| 446 | + "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2", |
| 447 | + "lang": "zh" |
| 448 | + }, |
| 449 | + { |
| 450 | + "name": "zh category-subcat-count 0,10", |
| 451 | + "key": "zh_category-subcat-count", |
| 452 | + "args": [ |
| 453 | + 0, |
| 454 | + 10 |
| 455 | + ], |
| 456 | + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002", |
| 457 | + "lang": "zh" |
| 458 | + }, |
| 459 | + { |
| 460 | + "name": "zh category-subcat-count 1,1", |
| 461 | + "key": "zh_category-subcat-count", |
| 462 | + "args": [ |
| 463 | + 1, |
| 464 | + 1 |
| 465 | + ], |
| 466 | + "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002", |
| 467 | + "lang": "zh" |
| 468 | + }, |
| 469 | + { |
| 470 | + "name": "zh category-subcat-count 1,2", |
| 471 | + "key": "zh_category-subcat-count", |
| 472 | + "args": [ |
| 473 | + 1, |
| 474 | + 2 |
| 475 | + ], |
| 476 | + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002", |
| 477 | + "lang": "zh" |
| 478 | + }, |
| 479 | + { |
| 480 | + "name": "zh category-subcat-count 3,30", |
| 481 | + "key": "zh_category-subcat-count", |
| 482 | + "args": [ |
| 483 | + 3, |
| 484 | + 30 |
| 485 | + ], |
| 486 | + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002", |
| 487 | + "lang": "zh" |
| 488 | + } |
| 489 | +]; |
Property changes on: trunk/phase3/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 490 | + native |
Index: trunk/phase3/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js |
— | — | @@ -0,0 +1,350 @@ |
| 2 | +/* spec for language & message behaviour in MediaWiki */ |
| 3 | + |
| 4 | +mw.messages.set( { |
| 5 | + "en_empty": "", |
| 6 | + "en_simple": "Simple message", |
| 7 | + "en_replace": "Simple $1 replacement", |
| 8 | + "en_replace2": "Simple $1 $2 replacements", |
| 9 | + "en_link": "Simple [http://example.com link to example].", |
| 10 | + "en_link_replace": "Complex [$1 $2] behaviour.", |
| 11 | + "en_simple_magic": "Simple {{ALOHOMORA}} message", |
| 12 | + "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", |
| 13 | + "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}", |
| 14 | + "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", |
| 15 | + "en_escape0": "Escape \\to fantasy island", |
| 16 | + "en_escape1": "I had \\$2.50 in my pocket", |
| 17 | + "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}", |
| 18 | + "en_fail": "This should fail to {{parse", |
| 19 | + "en_fail_magic": "There is no such magic word as {{SIETNAME}}" |
| 20 | +} ); |
| 21 | + |
| 22 | +/** |
| 23 | + * Tests |
| 24 | + */ |
| 25 | +( function( mw, $, undefined ) { |
| 26 | + |
| 27 | + describe( "mediaWiki.jqueryMsg", function() { |
| 28 | + |
| 29 | + describe( "basic message functionality", function() { |
| 30 | + |
| 31 | + it( "should return identity for empty string", function() { |
| 32 | + var parser = new mw.jqueryMsg.parser(); |
| 33 | + expect( parser.parse( 'en_empty' ).html() ).toEqual( '' ); |
| 34 | + } ); |
| 35 | + |
| 36 | + |
| 37 | + it( "should return identity for simple string", function() { |
| 38 | + var parser = new mw.jqueryMsg.parser(); |
| 39 | + expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' ); |
| 40 | + } ); |
| 41 | + |
| 42 | + } ); |
| 43 | + |
| 44 | + describe( "escaping", function() { |
| 45 | + |
| 46 | + it ( "should handle simple escaping", function() { |
| 47 | + var parser = new mw.jqueryMsg.parser(); |
| 48 | + expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' ); |
| 49 | + } ); |
| 50 | + |
| 51 | + it ( "should escape dollar signs found in ordinary text when backslashed", function() { |
| 52 | + var parser = new mw.jqueryMsg.parser(); |
| 53 | + expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' ); |
| 54 | + } ); |
| 55 | + |
| 56 | + it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() { |
| 57 | + var parser = new mw.jqueryMsg.parser(); |
| 58 | + expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' ); |
| 59 | + } ); |
| 60 | + |
| 61 | + } ); |
| 62 | + |
| 63 | + describe( "replacing", function() { |
| 64 | + |
| 65 | + it ( "should handle simple replacing", function() { |
| 66 | + var parser = new mw.jqueryMsg.parser(); |
| 67 | + expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' ); |
| 68 | + } ); |
| 69 | + |
| 70 | + it ( "should return $n if replacement not there", function() { |
| 71 | + var parser = new mw.jqueryMsg.parser(); |
| 72 | + expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' ); |
| 73 | + expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' ); |
| 74 | + } ); |
| 75 | + |
| 76 | + } ); |
| 77 | + |
| 78 | + describe( "linking", function() { |
| 79 | + |
| 80 | + it ( "should handle a simple link", function() { |
| 81 | + var parser = new mw.jqueryMsg.parser(); |
| 82 | + var parsed = parser.parse( 'en_link' ); |
| 83 | + var contents = parsed.contents(); |
| 84 | + expect( contents.length ).toEqual( 3 ); |
| 85 | + expect( contents[0].nodeName ).toEqual( '#text' ); |
| 86 | + expect( contents[0].nodeValue ).toEqual( 'Simple ' ); |
| 87 | + expect( contents[1].nodeName ).toEqual( 'A' ); |
| 88 | + expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' ); |
| 89 | + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' ); |
| 90 | + expect( contents[2].nodeName ).toEqual( '#text' ); |
| 91 | + expect( contents[2].nodeValue ).toEqual( '.' ); |
| 92 | + } ); |
| 93 | + |
| 94 | + it ( "should replace a URL into a link", function() { |
| 95 | + var parser = new mw.jqueryMsg.parser(); |
| 96 | + var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] ); |
| 97 | + var contents = parsed.contents(); |
| 98 | + expect( contents.length ).toEqual( 3 ); |
| 99 | + expect( contents[0].nodeName ).toEqual( '#text' ); |
| 100 | + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); |
| 101 | + expect( contents[1].nodeName ).toEqual( 'A' ); |
| 102 | + expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' ); |
| 103 | + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); |
| 104 | + expect( contents[2].nodeName ).toEqual( '#text' ); |
| 105 | + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); |
| 106 | + } ); |
| 107 | + |
| 108 | + it ( "should bind a click handler into a link", function() { |
| 109 | + var parser = new mw.jqueryMsg.parser(); |
| 110 | + var clicked = false; |
| 111 | + var click = function() { clicked = true; }; |
| 112 | + var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] ); |
| 113 | + var contents = parsed.contents(); |
| 114 | + expect( contents.length ).toEqual( 3 ); |
| 115 | + expect( contents[0].nodeName ).toEqual( '#text' ); |
| 116 | + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); |
| 117 | + expect( contents[1].nodeName ).toEqual( 'A' ); |
| 118 | + expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' ); |
| 119 | + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); |
| 120 | + expect( contents[2].nodeName ).toEqual( '#text' ); |
| 121 | + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); |
| 122 | + // determining bindings is hard in IE |
| 123 | + var anchor = parsed.find( 'a' ); |
| 124 | + if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) { |
| 125 | + expect( clicked ).toEqual( false ); |
| 126 | + anchor.click(); |
| 127 | + expect( clicked ).toEqual( true ); |
| 128 | + } |
| 129 | + } ); |
| 130 | + |
| 131 | + it ( "should wrap a jquery arg around link contents -- even another element", function() { |
| 132 | + var parser = new mw.jqueryMsg.parser(); |
| 133 | + var clicked = false; |
| 134 | + var click = function() { clicked = true; }; |
| 135 | + var button = $( '<button>' ).click( click ); |
| 136 | + var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] ); |
| 137 | + var contents = parsed.contents(); |
| 138 | + expect( contents.length ).toEqual( 3 ); |
| 139 | + expect( contents[0].nodeName ).toEqual( '#text' ); |
| 140 | + expect( contents[0].nodeValue ).toEqual( 'Complex ' ); |
| 141 | + expect( contents[1].nodeName ).toEqual( 'BUTTON' ); |
| 142 | + expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' ); |
| 143 | + expect( contents[2].nodeName ).toEqual( '#text' ); |
| 144 | + expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); |
| 145 | + // determining bindings is hard in IE |
| 146 | + if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) { |
| 147 | + expect( clicked ).toEqual( false ); |
| 148 | + parsed.find( 'button' ).click(); |
| 149 | + expect( clicked ).toEqual( true ); |
| 150 | + } |
| 151 | + } ); |
| 152 | + |
| 153 | + |
| 154 | + } ); |
| 155 | + |
| 156 | + |
| 157 | + describe( "magic keywords", function() { |
| 158 | + it( "should substitute magic keywords", function() { |
| 159 | + var options = { |
| 160 | + magic: { |
| 161 | + 'alohomora' : 'open' |
| 162 | + } |
| 163 | + }; |
| 164 | + var parser = new mw.jqueryMsg.parser( options ); |
| 165 | + expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' ); |
| 166 | + } ); |
| 167 | + } ); |
| 168 | + |
| 169 | + describe( "error conditions", function() { |
| 170 | + it( "should return non-existent key in square brackets", function() { |
| 171 | + var parser = new mw.jqueryMsg.parser(); |
| 172 | + expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' ); |
| 173 | + } ); |
| 174 | + |
| 175 | + |
| 176 | + it( "should fail to parse", function() { |
| 177 | + var parser = new mw.jqueryMsg.parser(); |
| 178 | + expect( function() { parser.parse( 'en_fail' ); } ).toThrow( |
| 179 | + 'Parse error at position 20 in input: This should fail to {{parse' |
| 180 | + ); |
| 181 | + } ); |
| 182 | + } ); |
| 183 | + |
| 184 | + describe( "empty parameters", function() { |
| 185 | + it( "should deal with empty parameters", function() { |
| 186 | + var parser = new mw.jqueryMsg.parser(); |
| 187 | + var ast = parser.getAst( 'en_undelete_empty_param' ); |
| 188 | + expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' ); |
| 189 | + expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' ); |
| 190 | + |
| 191 | + } ); |
| 192 | + } ); |
| 193 | + |
| 194 | + describe( "easy message interface functions", function() { |
| 195 | + it( "should allow a global that returns strings", function() { |
| 196 | + var gM = mw.jqueryMsg.getMessageFunction(); |
| 197 | + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. |
| 198 | + // a surrounding <SPAN> is needed for html() to work right |
| 199 | + var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html(); |
| 200 | + var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' ); |
| 201 | + expect( typeof result ).toEqual( 'string' ); |
| 202 | + expect( result ).toEqual( expectedHtml ); |
| 203 | + } ); |
| 204 | + |
| 205 | + it( "should allow a jQuery plugin that appends to nodes", function() { |
| 206 | + $.fn.msg = mw.jqueryMsg.getPlugin(); |
| 207 | + var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); |
| 208 | + var clicked = false; |
| 209 | + var $button = $( '<button>' ).click( function() { clicked = true; } ); |
| 210 | + $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' ); |
| 211 | + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. |
| 212 | + // a surrounding <SPAN> is needed for html() to work right |
| 213 | + var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html(); |
| 214 | + var createdHtml = $div.find( '.foo' ).html(); |
| 215 | + // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type. |
| 216 | + // so need to check the strings stripped of spaces. |
| 217 | + if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) { |
| 218 | + expect( createdHtml ).toEqual( expectedHtml ); |
| 219 | + $div.find( 'button ').click(); |
| 220 | + expect( clicked ).toEqual( true ); |
| 221 | + } else if ( $.browser.ie ) { |
| 222 | + expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) ); |
| 223 | + } |
| 224 | + delete $.fn.msg; |
| 225 | + } ); |
| 226 | + |
| 227 | + } ); |
| 228 | + |
| 229 | + // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have |
| 230 | + // a chance at fixing this |
| 231 | + describe( "easy message interface functions with graceful failures", function() { |
| 232 | + it( "should allow a global that returns strings, with graceful failure", function() { |
| 233 | + var gM = mw.jqueryMsg.getMessageFunction(); |
| 234 | + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. |
| 235 | + // a surrounding <SPAN> is needed for html() to work right |
| 236 | + var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); |
| 237 | + var result = gM( 'en_fail' ); |
| 238 | + expect( typeof result ).toEqual( 'string' ); |
| 239 | + expect( result ).toEqual( expectedHtml ); |
| 240 | + } ); |
| 241 | + |
| 242 | + it( "should allow a global that returns strings, with graceful failure on missing magic words", function() { |
| 243 | + var gM = mw.jqueryMsg.getMessageFunction(); |
| 244 | + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. |
| 245 | + // a surrounding <SPAN> is needed for html() to work right |
| 246 | + var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html(); |
| 247 | + var result = gM( 'en_fail_magic' ); |
| 248 | + expect( typeof result ).toEqual( 'string' ); |
| 249 | + expect( result ).toEqual( expectedHtml ); |
| 250 | + } ); |
| 251 | + |
| 252 | + |
| 253 | + it( "should allow a jQuery plugin, with graceful failure", function() { |
| 254 | + $.fn.msg = mw.jqueryMsg.getPlugin(); |
| 255 | + var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); |
| 256 | + $div.find( '.foo' ).msg( 'en_fail' ); |
| 257 | + // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. |
| 258 | + // a surrounding <SPAN> is needed for html() to work right |
| 259 | + var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); |
| 260 | + var createdHtml = $div.find( '.foo' ).html(); |
| 261 | + expect( createdHtml ).toEqual( expectedHtml ); |
| 262 | + delete $.fn.msg; |
| 263 | + } ); |
| 264 | + |
| 265 | + } ); |
| 266 | + |
| 267 | + |
| 268 | + |
| 269 | + |
| 270 | + describe( "test plurals and other language-specific functions", function() { |
| 271 | + /* copying some language definitions in here -- it's hard to make this test fast and reliable |
| 272 | + otherwise, and we don't want to have to know the mediawiki URL from this kind of test either. |
| 273 | + We also can't preload the langs for the test since they clobber the same namespace. |
| 274 | + In principle Roan said it was okay to change how languages worked so that didn't happen... maybe |
| 275 | + someday. We'd have to the same kind of importing of the default rules for most rules, or maybe |
| 276 | + come up with some kind of subclassing scheme for languages */ |
| 277 | + var languageClasses = { |
| 278 | + ar: { |
| 279 | + /** |
| 280 | + * Arabic (العربية) language functions |
| 281 | + */ |
| 282 | + |
| 283 | + convertPlural: function( count, forms ) { |
| 284 | + forms = mw.language.preConvertPlural( forms, 6 ); |
| 285 | + if ( count === 0 ) { |
| 286 | + return forms[0]; |
| 287 | + } |
| 288 | + if ( count == 1 ) { |
| 289 | + return forms[1]; |
| 290 | + } |
| 291 | + if ( count == 2 ) { |
| 292 | + return forms[2]; |
| 293 | + } |
| 294 | + if ( count % 100 >= 3 && count % 100 <= 10 ) { |
| 295 | + return forms[3]; |
| 296 | + } |
| 297 | + if ( count % 100 >= 11 && count % 100 <= 99 ) { |
| 298 | + return forms[4]; |
| 299 | + } |
| 300 | + return forms[5]; |
| 301 | + }, |
| 302 | + |
| 303 | + digitTransformTable: { |
| 304 | + '0': '٠', // ٠ |
| 305 | + '1': '١', // ١ |
| 306 | + '2': '٢', // ٢ |
| 307 | + '3': '٣', // ٣ |
| 308 | + '4': '٤', // ٤ |
| 309 | + '5': '٥', // ٥ |
| 310 | + '6': '٦', // ٦ |
| 311 | + '7': '٧', // ٧ |
| 312 | + '8': '٨', // ٨ |
| 313 | + '9': '٩', // ٩ |
| 314 | + '.': '٫', // ٫ wrong table ? |
| 315 | + ',': '٬' // ٬ |
| 316 | + } |
| 317 | + |
| 318 | + }, |
| 319 | + en: { }, |
| 320 | + fr: { |
| 321 | + convertPlural: function( count, forms ) { |
| 322 | + forms = mw.language.preConvertPlural( forms, 2 ); |
| 323 | + return ( count <= 1 ) ? forms[0] : forms[1]; |
| 324 | + } |
| 325 | + }, |
| 326 | + jp: { }, |
| 327 | + zh: { } |
| 328 | + }; |
| 329 | + |
| 330 | + /* simulate how the language classes override, or don't, the standard functions in mw.language */ |
| 331 | + $.each( languageClasses, function( langCode, rules ) { |
| 332 | + $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) { |
| 333 | + if ( typeof rules[ propertyName ] === 'undefined' ) { |
| 334 | + rules[ propertyName ] = mw.language[ propertyName ]; |
| 335 | + } |
| 336 | + } ); |
| 337 | + } ); |
| 338 | + |
| 339 | + $.each( jasmineMsgSpec, function( i, test ) { |
| 340 | + it( "should parse " + test.name, function() { |
| 341 | + // using language override so we don't have to muck with global namespace |
| 342 | + var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } ); |
| 343 | + var parsedHtml = parser.parse( test.key, test.args ).html(); |
| 344 | + expect( parsedHtml ).toEqual( test.result ); |
| 345 | + } ); |
| 346 | + } ); |
| 347 | + |
| 348 | + } ); |
| 349 | + |
| 350 | + } ); |
| 351 | +} )( window.mediaWiki, jQuery ); |
Property changes on: trunk/phase3/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 352 | + native |
Index: trunk/phase3/tests/jasmine/spec_makers/makeJqueryMsgSpec.php |
— | — | @@ -0,0 +1,114 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * This PHP script defines the spec that the Javascript message parser should conform to. |
| 6 | + * |
| 7 | + * It does this by looking up the results of various string kinds of string parsing, with various languages, |
| 8 | + * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs, |
| 9 | + * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into |
| 10 | + * the SpecRunner.html file. |
| 11 | + * |
| 12 | + * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the |
| 13 | + * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare |
| 14 | + * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.) |
| 15 | + * |
| 16 | + */ |
| 17 | + |
| 18 | +$maintenanceDir = dirname( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ) . '/maintenance'; |
| 19 | + |
| 20 | +require( "$maintenanceDir/Maintenance.php" ); |
| 21 | + |
| 22 | +class MakeLanguageSpec extends Maintenance { |
| 23 | + |
| 24 | + static $keyToTestArgs = array( |
| 25 | + 'undelete_short' => array( |
| 26 | + array( 0 ), |
| 27 | + array( 1 ), |
| 28 | + array( 2 ), |
| 29 | + array( 5 ), |
| 30 | + array( 21 ), |
| 31 | + array( 101 ) |
| 32 | + ), |
| 33 | + 'category-subcat-count' => array( |
| 34 | + array( 0, 10 ), |
| 35 | + array( 1, 1 ), |
| 36 | + array( 1, 2 ), |
| 37 | + array( 3, 30 ) |
| 38 | + ) |
| 39 | + ); |
| 40 | + |
| 41 | + public function __construct() { |
| 42 | + parent::__construct(); |
| 43 | + $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing"; |
| 44 | + // add any other options here |
| 45 | + } |
| 46 | + |
| 47 | + public function execute() { |
| 48 | + list( $messages, $tests ) = $this->getMessagesAndTests(); |
| 49 | + $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" ); |
| 50 | + } |
| 51 | + |
| 52 | + private function getMessagesAndTests() { |
| 53 | + $messages = array(); |
| 54 | + $tests = array(); |
| 55 | + $wfMsgExtOptions = array( 'parsemag' ); |
| 56 | + foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) { |
| 57 | + $wfMsgExtOptions['language'] = $languageCode; |
| 58 | + foreach ( self::$keyToTestArgs as $key => $testArgs ) { |
| 59 | + foreach ($testArgs as $args) { |
| 60 | + // get the raw template, without any transformations |
| 61 | + $template = wfMsgGetKey( $key, /* useDb */ true, $languageCode, /* transform */ false ); |
| 62 | + |
| 63 | + // get the magic-parsed version with args |
| 64 | + $wfMsgExtArgs = array_merge( array( $key, $wfMsgExtOptions ), $args ); |
| 65 | + $result = call_user_func_array( 'wfMsgExt', $wfMsgExtArgs ); |
| 66 | + |
| 67 | + // record the template, args, language, and expected result |
| 68 | + // fake multiple languages by flattening them together |
| 69 | + $langKey = $languageCode . '_' . $key; |
| 70 | + $messages[ $langKey ] = $template; |
| 71 | + $tests[] = array( |
| 72 | + 'name' => $languageCode . " " . $key . " " . join( ",", $args ), |
| 73 | + 'key' => $langKey, |
| 74 | + 'args' => $args, |
| 75 | + 'result' => $result, |
| 76 | + 'lang' => $languageCode |
| 77 | + ); |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + return array( $messages, $tests ); |
| 82 | + } |
| 83 | + |
| 84 | + private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) { |
| 85 | + global $argv; |
| 86 | + $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ]; |
| 87 | + |
| 88 | + $json = new Services_JSON; |
| 89 | + $json->pretty = true; |
| 90 | + $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n" |
| 91 | + . "// so we can test the equivalent Javascript libraries.\n" |
| 92 | + . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n"; |
| 93 | + $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n"; |
| 94 | + $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n"; |
| 95 | + |
| 96 | + $fp = fopen( $dataSpecFile, 'w' ); |
| 97 | + if ( !$fp ) { |
| 98 | + die( "couldn't open $dataSpecFile for writing" ); |
| 99 | + } |
| 100 | + $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests ); |
| 101 | + if ( !$success ) { |
| 102 | + die( "couldn't write to $dataSpecFile" ); |
| 103 | + } |
| 104 | + $success = fclose( $fp ); |
| 105 | + if ( !$success ) { |
| 106 | + die( "couldn't close $dataSpecFile" ); |
| 107 | + } |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +$maintClass = "MakeLanguageSpec"; |
| 112 | +require_once( "$maintenanceDir/doMaintenance.php" ); |
| 113 | + |
| 114 | + |
| 115 | + |
Property changes on: trunk/phase3/tests/jasmine/spec_makers/makeJqueryMsgSpec.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 116 | + native |
Index: trunk/phase3/resources/mediawiki/mediawiki.jqueryMsg.peg |
— | — | @@ -0,0 +1,76 @@ |
| 2 | +/* PEG grammar for a subset of wikitext, useful in the MediaWiki frontend */ |
| 3 | + |
| 4 | +start |
| 5 | + = e:expression* { return e.length > 1 ? [ "CONCAT" ].concat(e) : e[0]; } |
| 6 | + |
| 7 | +expression |
| 8 | + = template |
| 9 | + / link |
| 10 | + / extlink |
| 11 | + / replacement |
| 12 | + / literal |
| 13 | + |
| 14 | +paramExpression |
| 15 | + = template |
| 16 | + / link |
| 17 | + / extlink |
| 18 | + / replacement |
| 19 | + / literalWithoutBar |
| 20 | + |
| 21 | +template |
| 22 | + = "{{" t:templateContents "}}" { return t; } |
| 23 | + |
| 24 | +templateContents |
| 25 | + = twr:templateWithReplacement p:templateParam* { return twr.concat(p) } |
| 26 | + / t:templateName p:templateParam* { return p.length ? [ t, p ] : [ t ] } |
| 27 | + |
| 28 | +templateWithReplacement |
| 29 | + = t:templateName ":" r:replacement { return [ t, r ] } |
| 30 | + |
| 31 | +templateParam |
| 32 | + = "|" e:paramExpression* { return e.length > 1 ? [ "CONCAT" ].concat(e) : e[0]; } |
| 33 | + |
| 34 | +templateName |
| 35 | + = tn:[A-Za-z_]+ { return tn.join('').toUpperCase() } |
| 36 | + |
| 37 | +link |
| 38 | + = "[[" w:expression "]]" { return [ 'WLINK', w ]; } |
| 39 | + |
| 40 | +extlink |
| 41 | + = "[" url:url whitespace text:expression "]" { return [ 'LINK', url, text ] } |
| 42 | + |
| 43 | +url |
| 44 | + = url:[^ ]+ { return url.join(''); } |
| 45 | + |
| 46 | +whitespace |
| 47 | + = [ ]+ |
| 48 | + |
| 49 | +replacement |
| 50 | + = '$' digits:digits { return [ 'REPLACE', parseInt( digits, 10 ) - 1 ] } |
| 51 | + |
| 52 | +digits |
| 53 | + = [0-9]+ |
| 54 | + |
| 55 | +literal |
| 56 | + = lit:escapedOrRegularLiteral+ { return lit.join(''); } |
| 57 | + |
| 58 | +literalWithoutBar |
| 59 | + = lit:escapedOrLiteralWithoutBar+ { return lit.join(''); } |
| 60 | + |
| 61 | +escapedOrRegularLiteral |
| 62 | + = escapedLiteral |
| 63 | + / regularLiteral |
| 64 | + |
| 65 | +escapedOrLiteralWithoutBar |
| 66 | + = escapedLiteral |
| 67 | + / regularLiteralWithoutBar |
| 68 | + |
| 69 | +escapedLiteral |
| 70 | + = "\\" escaped:. { return escaped; } |
| 71 | + |
| 72 | +regularLiteral |
| 73 | + = [^{}\[\]$\\] |
| 74 | + |
| 75 | +regularLiteralWithoutBar |
| 76 | + = [^{}\[\]$\\|] |
| 77 | + |
Index: trunk/phase3/resources/mediawiki/mediawiki.jqueryMsg.js |
— | — | @@ -0,0 +1,648 @@ |
| 2 | +/** |
| 3 | + * Experimental advanced wikitext parser-emitter. |
| 4 | + * See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs |
| 5 | + * |
| 6 | + * @author neilk@wikimedia.org |
| 7 | + */ |
| 8 | + |
| 9 | +( function( mw, $, undefined ) { |
| 10 | + |
| 11 | + mw.jqueryMsg = {}; |
| 12 | + |
| 13 | + /** |
| 14 | + * Given parser options, return a function that parses a key and replacements, returning jQuery object |
| 15 | + * @param {Object} parser options |
| 16 | + * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery} |
| 17 | + */ |
| 18 | + function getFailableParserFn( options ) { |
| 19 | + var parser = new mw.jqueryMsg.parser( options ); |
| 20 | + /** |
| 21 | + * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes. |
| 22 | + * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into |
| 23 | + * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it. |
| 24 | + * |
| 25 | + * @param {Array} first element is the key, replacements may be in array in 2nd element, or remaining elements. |
| 26 | + * @return {jQuery} |
| 27 | + */ |
| 28 | + return function( args ) { |
| 29 | + var key = args[0]; |
| 30 | + var replacements = $.isArray( args[1] ) ? args[1] : $.makeArray( args ).slice( 1 ); |
| 31 | + try { |
| 32 | + return parser.parse( key, replacements ); |
| 33 | + } catch ( e ) { |
| 34 | + return $( '<span></span>' ).append( key + ': ' + e.message ); |
| 35 | + } |
| 36 | + }; |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * Class method. |
| 41 | + * Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements). |
| 42 | + * e.g. |
| 43 | + * window.gM = mediaWiki.parser.getMessageFunction( options ); |
| 44 | + * $( 'p#headline' ).html( gM( 'hello-user', username ) ); |
| 45 | + * |
| 46 | + * Like the old gM() function this returns only strings, so it destroys any bindings. If you want to preserve bindings use the |
| 47 | + * jQuery plugin version instead. This is only included for backwards compatibility with gM(). |
| 48 | + * |
| 49 | + * @param {Array} parser options |
| 50 | + * @return {Function} function suitable for assigning to window.gM |
| 51 | + */ |
| 52 | + mw.jqueryMsg.getMessageFunction = function( options ) { |
| 53 | + var failableParserFn = getFailableParserFn( options ); |
| 54 | + /** |
| 55 | + * N.B. replacements are variadic arguments or an array in second parameter. In other words: |
| 56 | + * somefunction(a, b, c, d) |
| 57 | + * is equivalent to |
| 58 | + * somefunction(a, [b, c, d]) |
| 59 | + * |
| 60 | + * @param {String} message key |
| 61 | + * @param {Array} optional replacements (can also specify variadically) |
| 62 | + * @return {String} rendered HTML as string |
| 63 | + */ |
| 64 | + return function( /* key, replacements */ ) { |
| 65 | + return failableParserFn( arguments ).html(); |
| 66 | + }; |
| 67 | + }; |
| 68 | + |
| 69 | + /** |
| 70 | + * Class method. |
| 71 | + * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to |
| 72 | + * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links. |
| 73 | + * e.g. |
| 74 | + * $.fn.msg = mediaWiki.parser.getJqueryPlugin( options ); |
| 75 | + * var userlink = $( '<a>' ).click( function() { alert( "hello!!") } ); |
| 76 | + * $( 'p#headline' ).msg( 'hello-user', userlink ); |
| 77 | + * |
| 78 | + * @param {Array} parser options |
| 79 | + * @return {Function} function suitable for assigning to jQuery plugin, such as $.fn.msg |
| 80 | + */ |
| 81 | + mw.jqueryMsg.getPlugin = function( options ) { |
| 82 | + var failableParserFn = getFailableParserFn( options ); |
| 83 | + /** |
| 84 | + * N.B. replacements are variadic arguments or an array in second parameter. In other words: |
| 85 | + * somefunction(a, b, c, d) |
| 86 | + * is equivalent to |
| 87 | + * somefunction(a, [b, c, d]) |
| 88 | + * |
| 89 | + * We append to 'this', which in a jQuery plugin context will be the selected elements. |
| 90 | + * @param {String} message key |
| 91 | + * @param {Array} optional replacements (can also specify variadically) |
| 92 | + * @return {jQuery} this |
| 93 | + */ |
| 94 | + return function( /* key, replacements */ ) { |
| 95 | + var $target = this.empty(); |
| 96 | + $.each( failableParserFn( arguments ).contents(), function( i, node ) { |
| 97 | + $target.append( node ); |
| 98 | + } ); |
| 99 | + return $target; |
| 100 | + }; |
| 101 | + }; |
| 102 | + |
| 103 | + var parserDefaults = { |
| 104 | + 'magic' : {}, |
| 105 | + 'messages' : mw.messages, |
| 106 | + 'language' : mw.language |
| 107 | + }; |
| 108 | + |
| 109 | + /** |
| 110 | + * The parser itself. |
| 111 | + * Describes an object, whose primary duty is to .parse() message keys. |
| 112 | + * @param {Array} options |
| 113 | + */ |
| 114 | + mw.jqueryMsg.parser = function( options ) { |
| 115 | + this.settings = $.extend( {}, parserDefaults, options ); |
| 116 | + this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic ); |
| 117 | + }; |
| 118 | + |
| 119 | + mw.jqueryMsg.parser.prototype = { |
| 120 | + |
| 121 | + // cache, map of mediaWiki message key to the AST of the message. In most cases, the message is a string so this is identical. |
| 122 | + // (This is why we would like to move this functionality server-side). |
| 123 | + astCache: {}, |
| 124 | + |
| 125 | + /** |
| 126 | + * Where the magic happens. |
| 127 | + * Parses a message from the key, and swaps in replacements as necessary, wraps in jQuery |
| 128 | + * If an error is thrown, returns original key, and logs the error |
| 129 | + * @param {String} message key |
| 130 | + * @param {Array} replacements for $1, $2... $n |
| 131 | + * @return {jQuery} |
| 132 | + */ |
| 133 | + parse: function( key, replacements ) { |
| 134 | + return this.emitter.emit( this.getAst( key ), replacements ); |
| 135 | + }, |
| 136 | + |
| 137 | + /** |
| 138 | + * Fetch the message string associated with a key, return parsed structure. Memoized. |
| 139 | + * Note that we pass '[' + key + ']' back for a missing message here. |
| 140 | + * @param {String} key |
| 141 | + * @return {String|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing |
| 142 | + */ |
| 143 | + getAst: function( key ) { |
| 144 | + if ( this.astCache[ key ] === undefined ) { |
| 145 | + var wikiText = this.settings.messages.get( key ); |
| 146 | + if ( typeof wikiText !== 'string' ) { |
| 147 | + wikiText = "\\[" + key + "\\]"; |
| 148 | + } |
| 149 | + this.astCache[ key ] = this.wikiTextToAst( wikiText ); |
| 150 | + } |
| 151 | + return this.astCache[ key ]; |
| 152 | + }, |
| 153 | + |
| 154 | + /* |
| 155 | + * Parses the input wikiText into an abstract syntax tree, essentially an s-expression. |
| 156 | + * |
| 157 | + * CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already. |
| 158 | + * n.b. We want to move this functionality to the server. Nothing here is required to be on the client. |
| 159 | + * |
| 160 | + * @param {String} message string wikitext |
| 161 | + * @throws Error |
| 162 | + * @return {Mixed} abstract syntax tree |
| 163 | + */ |
| 164 | + wikiTextToAst: function( input ) { |
| 165 | + |
| 166 | + // Indicates current position in input as we parse through it. |
| 167 | + // Shared among all parsing functions below. |
| 168 | + var pos = 0; |
| 169 | + |
| 170 | + // ========================================================= |
| 171 | + // parsing combinators - could be a library on its own |
| 172 | + // ========================================================= |
| 173 | + |
| 174 | + |
| 175 | + // Try parsers until one works, if none work return null |
| 176 | + function choice( ps ) { |
| 177 | + return function() { |
| 178 | + for ( var i = 0; i < ps.length; i++ ) { |
| 179 | + var result = ps[i](); |
| 180 | + if ( result !== null ) { |
| 181 | + return result; |
| 182 | + } |
| 183 | + } |
| 184 | + return null; |
| 185 | + }; |
| 186 | + } |
| 187 | + |
| 188 | + // try several ps in a row, all must succeed or return null |
| 189 | + // this is the only eager one |
| 190 | + function sequence( ps ) { |
| 191 | + var originalPos = pos; |
| 192 | + var result = []; |
| 193 | + for ( var i = 0; i < ps.length; i++ ) { |
| 194 | + var res = ps[i](); |
| 195 | + if ( res === null ) { |
| 196 | + pos = originalPos; |
| 197 | + return null; |
| 198 | + } |
| 199 | + result.push( res ); |
| 200 | + } |
| 201 | + return result; |
| 202 | + } |
| 203 | + |
| 204 | + // run the same parser over and over until it fails. |
| 205 | + // must succeed a minimum of n times or return null |
| 206 | + function nOrMore( n, p ) { |
| 207 | + return function() { |
| 208 | + var originalPos = pos; |
| 209 | + var result = []; |
| 210 | + var parsed = p(); |
| 211 | + while ( parsed !== null ) { |
| 212 | + result.push( parsed ); |
| 213 | + parsed = p(); |
| 214 | + } |
| 215 | + if ( result.length < n ) { |
| 216 | + pos = originalPos; |
| 217 | + return null; |
| 218 | + } |
| 219 | + return result; |
| 220 | + }; |
| 221 | + } |
| 222 | + |
| 223 | + // There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null. |
| 224 | + // But using this as a combinator seems to cause problems when combined with nOrMore(). |
| 225 | + // May be some scoping issue |
| 226 | + function transform( p, fn ) { |
| 227 | + return function() { |
| 228 | + var result = p(); |
| 229 | + return result === null ? null : fn( result ); |
| 230 | + }; |
| 231 | + } |
| 232 | + |
| 233 | + // Helpers -- just make ps out of simpler JS builtin types |
| 234 | + |
| 235 | + function makeStringParser( s ) { |
| 236 | + var len = s.length; |
| 237 | + return function() { |
| 238 | + var result = null; |
| 239 | + if ( input.substr( pos, len ) === s ) { |
| 240 | + result = s; |
| 241 | + pos += len; |
| 242 | + } |
| 243 | + return result; |
| 244 | + }; |
| 245 | + } |
| 246 | + |
| 247 | + function makeRegexParser( regex ) { |
| 248 | + return function() { |
| 249 | + var matches = input.substr( pos ).match( regex ); |
| 250 | + if ( matches === null ) { |
| 251 | + return null; |
| 252 | + } |
| 253 | + pos += matches[0].length; |
| 254 | + return matches[0]; |
| 255 | + }; |
| 256 | + } |
| 257 | + |
| 258 | + |
| 259 | + /** |
| 260 | + * =================================================================== |
| 261 | + * General patterns above this line -- wikitext specific parsers below |
| 262 | + * =================================================================== |
| 263 | + */ |
| 264 | + |
| 265 | + // Parsing functions follow. All parsing functions work like this: |
| 266 | + // They don't accept any arguments. |
| 267 | + // Instead, they just operate non destructively on the string 'input' |
| 268 | + // As they can consume parts of the string, they advance the shared variable pos, |
| 269 | + // and return tokens (or whatever else they want to return). |
| 270 | + |
| 271 | + // some things are defined as closures and other things as ordinary functions |
| 272 | + // converting everything to a closure makes it a lot harder to debug... errors pop up |
| 273 | + // but some debuggers can't tell you exactly where they come from. Also the mutually |
| 274 | + // recursive functions seem not to work in all browsers then. (Tested IE6-7, Opera, Safari, FF) |
| 275 | + // This may be because, to save code, memoization was removed |
| 276 | + |
| 277 | + |
| 278 | + var regularLiteral = makeRegexParser( /^[^{}[\]$\\]/ ); |
| 279 | + var regularLiteralWithoutBar = makeRegexParser(/^[^{}[\]$\\|]/); |
| 280 | + var regularLiteralWithoutSpace = makeRegexParser(/^[^{}[\]$\s]/); |
| 281 | + |
| 282 | + var backslash = makeStringParser( "\\" ); |
| 283 | + var anyCharacter = makeRegexParser( /^./ ); |
| 284 | + |
| 285 | + function escapedLiteral() { |
| 286 | + var result = sequence( [ |
| 287 | + backslash, |
| 288 | + anyCharacter |
| 289 | + ] ); |
| 290 | + return result === null ? null : result[1]; |
| 291 | + } |
| 292 | + |
| 293 | + var escapedOrLiteralWithoutSpace = choice( [ |
| 294 | + escapedLiteral, |
| 295 | + regularLiteralWithoutSpace |
| 296 | + ] ); |
| 297 | + |
| 298 | + var escapedOrLiteralWithoutBar = choice( [ |
| 299 | + escapedLiteral, |
| 300 | + regularLiteralWithoutBar |
| 301 | + ] ); |
| 302 | + |
| 303 | + var escapedOrRegularLiteral = choice( [ |
| 304 | + escapedLiteral, |
| 305 | + regularLiteral |
| 306 | + ] ); |
| 307 | + |
| 308 | + // Used to define "literals" without spaces, in space-delimited situations |
| 309 | + function literalWithoutSpace() { |
| 310 | + var result = nOrMore( 1, escapedOrLiteralWithoutSpace )(); |
| 311 | + return result === null ? null : result.join(''); |
| 312 | + } |
| 313 | + |
| 314 | + // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default |
| 315 | + // it is not a literal in the parameter |
| 316 | + function literalWithoutBar() { |
| 317 | + var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); |
| 318 | + return result === null ? null : result.join(''); |
| 319 | + } |
| 320 | + |
| 321 | + function literal() { |
| 322 | + var result = nOrMore( 1, escapedOrRegularLiteral )(); |
| 323 | + return result === null ? null : result.join(''); |
| 324 | + } |
| 325 | + |
| 326 | + var whitespace = makeRegexParser( /^\s+/ ); |
| 327 | + var dollar = makeStringParser( '$' ); |
| 328 | + var digits = makeRegexParser( /^\d+/ ); |
| 329 | + |
| 330 | + function replacement() { |
| 331 | + var result = sequence( [ |
| 332 | + dollar, |
| 333 | + digits |
| 334 | + ] ); |
| 335 | + if ( result === null ) { |
| 336 | + return null; |
| 337 | + } |
| 338 | + return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ]; |
| 339 | + } |
| 340 | + |
| 341 | + |
| 342 | + var openExtlink = makeStringParser( '[' ); |
| 343 | + var closeExtlink = makeStringParser( ']' ); |
| 344 | + |
| 345 | + // this extlink MUST have inner text, e.g. [foo] not allowed; [foo bar] is allowed |
| 346 | + function extlink() { |
| 347 | + var result = null; |
| 348 | + var parsedResult = sequence( [ |
| 349 | + openExtlink, |
| 350 | + nonWhitespaceExpression, |
| 351 | + whitespace, |
| 352 | + expression, |
| 353 | + closeExtlink |
| 354 | + ] ); |
| 355 | + if ( parsedResult !== null ) { |
| 356 | + result = [ 'LINK', parsedResult[1], parsedResult[3] ]; |
| 357 | + } |
| 358 | + return result; |
| 359 | + } |
| 360 | + |
| 361 | + var openLink = makeStringParser( '[[' ); |
| 362 | + var closeLink = makeStringParser( ']]' ); |
| 363 | + |
| 364 | + function link() { |
| 365 | + var result = null; |
| 366 | + var parsedResult = sequence( [ |
| 367 | + openLink, |
| 368 | + expression, |
| 369 | + closeLink |
| 370 | + ] ); |
| 371 | + if ( parsedResult !== null ) { |
| 372 | + result = [ 'WLINK', parsedResult[1] ]; |
| 373 | + } |
| 374 | + return result; |
| 375 | + } |
| 376 | + |
| 377 | + var templateName = transform( |
| 378 | + // see $wgLegalTitleChars |
| 379 | + // not allowing : due to the need to catch "PLURAL:$1" |
| 380 | + makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+-]+/ ), |
| 381 | + function( result ) { return result.toString(); } |
| 382 | + ); |
| 383 | + |
| 384 | + function templateParam() { |
| 385 | + var result = sequence( [ |
| 386 | + pipe, |
| 387 | + nOrMore( 0, paramExpression ) |
| 388 | + ] ); |
| 389 | + if ( result === null ) { |
| 390 | + return null; |
| 391 | + } |
| 392 | + var expr = result[1]; |
| 393 | + // use a "CONCAT" operator if there are multiple nodes, otherwise return the first node, raw. |
| 394 | + return expr.length > 1 ? [ "CONCAT" ].concat( expr ) : expr[0]; |
| 395 | + } |
| 396 | + |
| 397 | + var pipe = makeStringParser( '|' ); |
| 398 | + |
| 399 | + function templateWithReplacement() { |
| 400 | + var result = sequence( [ |
| 401 | + templateName, |
| 402 | + colon, |
| 403 | + replacement |
| 404 | + ] ); |
| 405 | + return result === null ? null : [ result[0], result[2] ]; |
| 406 | + } |
| 407 | + |
| 408 | + var colon = makeStringParser(':'); |
| 409 | + |
| 410 | + var templateContents = choice( [ |
| 411 | + function() { |
| 412 | + var res = sequence( [ |
| 413 | + templateWithReplacement, |
| 414 | + nOrMore( 0, templateParam ) |
| 415 | + ] ); |
| 416 | + return res === null ? null : res[0].concat( res[1] ); |
| 417 | + }, |
| 418 | + function() { |
| 419 | + var res = sequence( [ |
| 420 | + templateName, |
| 421 | + nOrMore( 0, templateParam ) |
| 422 | + ] ); |
| 423 | + if ( res === null ) { |
| 424 | + return null; |
| 425 | + } |
| 426 | + return [ res[0] ].concat( res[1] ); |
| 427 | + } |
| 428 | + ] ); |
| 429 | + |
| 430 | + var openTemplate = makeStringParser('{{'); |
| 431 | + var closeTemplate = makeStringParser('}}'); |
| 432 | + |
| 433 | + function template() { |
| 434 | + var result = sequence( [ |
| 435 | + openTemplate, |
| 436 | + templateContents, |
| 437 | + closeTemplate |
| 438 | + ] ); |
| 439 | + return result === null ? null : result[1]; |
| 440 | + } |
| 441 | + |
| 442 | + var nonWhitespaceExpression = choice( [ |
| 443 | + template, |
| 444 | + link, |
| 445 | + extlink, |
| 446 | + replacement, |
| 447 | + literalWithoutSpace |
| 448 | + ] ); |
| 449 | + |
| 450 | + var paramExpression = choice( [ |
| 451 | + template, |
| 452 | + link, |
| 453 | + extlink, |
| 454 | + replacement, |
| 455 | + literalWithoutBar |
| 456 | + ] ); |
| 457 | + |
| 458 | + var expression = choice( [ |
| 459 | + template, |
| 460 | + link, |
| 461 | + extlink, |
| 462 | + replacement, |
| 463 | + literal |
| 464 | + ] ); |
| 465 | + |
| 466 | + function start() { |
| 467 | + var result = nOrMore( 0, expression )(); |
| 468 | + if ( result === null ) { |
| 469 | + return null; |
| 470 | + } |
| 471 | + return [ "CONCAT" ].concat( result ); |
| 472 | + } |
| 473 | + |
| 474 | + // everything above this point is supposed to be stateless/static, but |
| 475 | + // I am deferring the work of turning it into prototypes & objects. It's quite fast enough |
| 476 | + |
| 477 | + // finally let's do some actual work... |
| 478 | + |
| 479 | + var result = start(); |
| 480 | + |
| 481 | + /* |
| 482 | + * For success, the p must have gotten to the end of the input |
| 483 | + * and returned a non-null. |
| 484 | + * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. |
| 485 | + */ |
| 486 | + if (result === null || pos !== input.length) { |
| 487 | + throw new Error( "Parse error at position " + pos.toString() + " in input: " + input ); |
| 488 | + } |
| 489 | + return result; |
| 490 | + } |
| 491 | + |
| 492 | + }; |
| 493 | + |
| 494 | + /** |
| 495 | + * htmlEmitter - object which primarily exists to emit HTML from parser ASTs |
| 496 | + */ |
| 497 | + mw.jqueryMsg.htmlEmitter = function( language, magic ) { |
| 498 | + this.language = language; |
| 499 | + var _this = this; |
| 500 | + |
| 501 | + $.each( magic, function( key, val ) { |
| 502 | + _this[ key.toLowerCase() ] = function() { return val; }; |
| 503 | + } ); |
| 504 | + |
| 505 | + /** |
| 506 | + * (We put this method definition here, and not in prototype, to make sure it's not overwritten by any magic.) |
| 507 | + * Walk entire node structure, applying replacements and template functions when appropriate |
| 508 | + * @param {Mixed} abstract syntax tree (top node or subnode) |
| 509 | + * @param {Array} replacements for $1, $2, ... $n |
| 510 | + * @return {Mixed} single-string node or array of nodes suitable for jQuery appending |
| 511 | + */ |
| 512 | + this.emit = function( node, replacements ) { |
| 513 | + var ret = null; |
| 514 | + var _this = this; |
| 515 | + switch( typeof node ) { |
| 516 | + case 'string': |
| 517 | + case 'number': |
| 518 | + ret = node; |
| 519 | + break; |
| 520 | + case 'object': // node is an array of nodes |
| 521 | + var subnodes = $.map( node.slice( 1 ), function( n ) { |
| 522 | + return _this.emit( n, replacements ); |
| 523 | + } ); |
| 524 | + var operation = node[0].toLowerCase(); |
| 525 | + if ( typeof _this[operation] === 'function' ) { |
| 526 | + ret = _this[ operation ]( subnodes, replacements ); |
| 527 | + } else { |
| 528 | + throw new Error( 'unknown operation "' + operation + '"' ); |
| 529 | + } |
| 530 | + break; |
| 531 | + case 'undefined': |
| 532 | + // Parsing the empty string (as an entire expression, or as a paramExpression in a template) results in undefined |
| 533 | + // Perhaps a more clever parser can detect this, and return the empty string? Or is that useful information? |
| 534 | + // The logical thing is probably to return the empty string here when we encounter undefined. |
| 535 | + ret = ''; |
| 536 | + break; |
| 537 | + default: |
| 538 | + throw new Error( 'unexpected type in AST: ' + typeof node ); |
| 539 | + } |
| 540 | + return ret; |
| 541 | + }; |
| 542 | + |
| 543 | + }; |
| 544 | + |
| 545 | + // For everything in input that follows double-open-curly braces, there should be an equivalent parser |
| 546 | + // function. For instance {{PLURAL ... }} will be processed by 'plural'. |
| 547 | + // If you have 'magic words' then configure the parser to have them upon creation. |
| 548 | + // |
| 549 | + // An emitter method takes the parent node, the array of subnodes and the array of replacements (the values that $1, $2... should translate to). |
| 550 | + // Note: all such functions must be pure, with the exception of referring to other pure functions via this.language (convertPlural and so on) |
| 551 | + mw.jqueryMsg.htmlEmitter.prototype = { |
| 552 | + |
| 553 | + /** |
| 554 | + * Parsing has been applied depth-first we can assume that all nodes here are single nodes |
| 555 | + * Must return a single node to parents -- a jQuery with synthetic span |
| 556 | + * However, unwrap any other synthetic spans in our children and pass them upwards |
| 557 | + * @param {Array} nodes - mixed, some single nodes, some arrays of nodes |
| 558 | + * @return {jQuery} |
| 559 | + */ |
| 560 | + concat: function( nodes ) { |
| 561 | + var span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' ); |
| 562 | + $.each( nodes, function( i, node ) { |
| 563 | + if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) { |
| 564 | + $.each( node.contents(), function( j, childNode ) { |
| 565 | + span.append( childNode ); |
| 566 | + } ); |
| 567 | + } else { |
| 568 | + // strings, integers, anything else |
| 569 | + span.append( node ); |
| 570 | + } |
| 571 | + } ); |
| 572 | + return span; |
| 573 | + }, |
| 574 | + |
| 575 | + /** |
| 576 | + * Return replacement of correct index, or string if unavailable. |
| 577 | + * Note that we expect the parsed parameter to be zero-based. i.e. $1 should have become [ 0 ]. |
| 578 | + * if the specified parameter is not found return the same string |
| 579 | + * (e.g. "$99" -> parameter 98 -> not found -> return "$99" ) |
| 580 | + * TODO throw error if nodes.length > 1 ? |
| 581 | + * @param {Array} of one element, integer, n >= 0 |
| 582 | + * @return {String} replacement |
| 583 | + */ |
| 584 | + replace: function( nodes, replacements ) { |
| 585 | + var index = parseInt( nodes[0], 10 ); |
| 586 | + return index < replacements.length ? replacements[index] : '$' + ( index + 1 ); |
| 587 | + }, |
| 588 | + |
| 589 | + /** |
| 590 | + * Transform wiki-link |
| 591 | + * TODO unimplemented |
| 592 | + */ |
| 593 | + wlink: function( nodes ) { |
| 594 | + return "unimplemented"; |
| 595 | + }, |
| 596 | + |
| 597 | + /** |
| 598 | + * Transform parsed structure into external link |
| 599 | + * If the href is a jQuery object, treat it as "enclosing" the link text. |
| 600 | + * ... function, treat it as the click handler |
| 601 | + * ... string, treat it as a URI |
| 602 | + * TODO: throw an error if nodes.length > 2 ? |
| 603 | + * @param {Array} of two elements, {jQuery|Function|String} and {String} |
| 604 | + * @return {jQuery} |
| 605 | + */ |
| 606 | + link: function( nodes ) { |
| 607 | + var arg = nodes[0]; |
| 608 | + var contents = nodes[1]; |
| 609 | + var $el; |
| 610 | + if ( arg instanceof jQuery ) { |
| 611 | + $el = arg; |
| 612 | + } else { |
| 613 | + $el = $( '<a>' ); |
| 614 | + if ( typeof arg === 'function' ) { |
| 615 | + $el.click( arg ).attr( 'href', '#' ); |
| 616 | + } else { |
| 617 | + $el.attr( 'href', arg.toString() ); |
| 618 | + } |
| 619 | + } |
| 620 | + $el.append( contents ); |
| 621 | + return $el; |
| 622 | + }, |
| 623 | + |
| 624 | + /** |
| 625 | + * Transform parsed structure into pluralization |
| 626 | + * n.b. The first node may be a non-integer (for instance, a string representing an Arabic number). |
| 627 | + * So convert it back with the current language's convertNumber. |
| 628 | + * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ] |
| 629 | + * @return {String} selected pluralized form according to current language |
| 630 | + */ |
| 631 | + plural: function( nodes ) { |
| 632 | + var count = parseInt( this.language.convertNumber( nodes[0], true ), 10 ); |
| 633 | + var forms = nodes.slice(1); |
| 634 | + return forms.length ? this.language.convertPlural( count, forms ) : ''; |
| 635 | + } |
| 636 | + |
| 637 | + }; |
| 638 | + |
| 639 | + // TODO figure out a way to make magic work with common globals like wgSiteName, without requiring init from library users... |
| 640 | + // var options = { magic: { 'SITENAME' : mw.config.get( 'wgSiteName' ) } }; |
| 641 | + |
| 642 | + // deprecated! don't rely on gM existing. |
| 643 | + // the window.gM ought not to be required - or if required, not required here. But moving it to extensions breaks it (?!) |
| 644 | + // Need to fix plugin so it could do attributes as well, then will be okay to remove this. |
| 645 | + window.gM = mw.jqueryMsg.getMessageFunction(); |
| 646 | + |
| 647 | + $.fn.msg = mw.jqueryMsg.getPlugin(); |
| 648 | + |
| 649 | +} )( mediaWiki, jQuery ); |
Property changes on: trunk/phase3/resources/mediawiki/mediawiki.jqueryMsg.js |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 650 | + native |