├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── vue-intl.dev.js ├── vue-intl.dev.js.map ├── vue-intl.prod.min.js └── vue-intl.prod.min.js.map ├── package.json ├── rollup.config.dist.js ├── scripts ├── .babelrc └── build-data.js ├── src ├── .babelrc ├── FormattedMessage.js ├── format.js ├── getLocaleData.js ├── index.js ├── localeData.js ├── plural.js ├── setLocale.js ├── state.js ├── types.js ├── utils.js └── vue-intl.js └── test ├── .babelrc ├── mocha.opts └── unit ├── format.js ├── localeData.js ├── setLocale.js ├── types.js └── vue-intl.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/**/*.js 2 | dist/**/*.js 3 | locale-data/**/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "extends": "eslint:recommended", 7 | "env": { 8 | "es6": true, 9 | "node": true, 10 | "browser": true, 11 | "mocha": true 12 | }, 13 | "rules": { 14 | "no-console": 0, 15 | "no-unused-vars": 0, 16 | "comma-dangle": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | locale-data 4 | src/en.js 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | - 10 6 | script: npm run test:cov 7 | after_success: 8 | - bash <(curl -s https://codecov.io/bash) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Learning Equality 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-intl 2 | This repository has been archived, in favour of [FormatJS](https://github.com/formatjs/formatjs) which now hosts the vue-intl plugin. 3 | 4 | Vue Plugin for FormatJS Internalization and Localization 5 | 6 | 7 | ### Install 8 | 9 | ``` bash 10 | npm install vue-intl 11 | ``` 12 | 13 | ### Usage 14 | 15 | ``` js 16 | // assuming CommonJS 17 | var Vue = require('vue'); 18 | var VueIntl = require('vue-intl'); 19 | 20 | // use globally 21 | Vue.use(VueIntl); 22 | ``` 23 | 24 | *N.B.* The underlying suite, FormatJS, that the VueIntl plugin relies on, requires either a browser that supports the 25 | [Intl API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), or has the [Intl Polyfill](https://github.com/andyearnshaw/Intl.js/) 26 | available. As such, it is necessary for cross browser support to load the Intl polyfill (or preferably to load it if needed). 27 | 28 | See the [FormatJS Documentation](http://formatjs.io/guides/runtime-environments/) for more details. 29 | 30 | ### Global API Methods 31 | 32 | #### setLocale 33 | 34 | Set the current locale for the page. 35 | 36 | ``` js 37 | Vue.setLocale('fr'); 38 | ``` 39 | 40 | Alternatively, use a more specific locale code. 41 | 42 | ``` js 43 | Vue.setLocale('en-GB'); 44 | ``` 45 | 46 | #### registerMessages 47 | 48 | Set an object containing messages for a particular locale. 49 | 50 | ``` js 51 | Vue.registerMessages('fr', { 52 | example_message_id: "La plume de ma tante est sur le bureau de mon oncle." 53 | }); 54 | ``` 55 | 56 | This message will now be available when the locale is set to 'fr'. 57 | 58 | #### registerFormats 59 | 60 | Create custom formats, see [FormatJS main documentation for details](http://formatjs.io/guides/message-syntax/#custom-formats). 61 | 62 | ``` js 63 | Vue.registerFormats('fr', { 64 | number: { 65 | eur: { style: 'currency', currency: 'EUR' } 66 | } 67 | }); 68 | ``` 69 | 70 | This format will now be available when the locale is set to 'fr'. 71 | 72 | ### Instance Methods 73 | 74 | These methods are for actually performing localization and translation within the context of a Vue component. 75 | 76 | The methods are set on the Vue instance prototype, so are available locally, with access to local variables. 77 | 78 | #### $formatDate 79 | 80 | This will format dates to the locale appropriate format. 81 | 82 | 83 | ```html 84 |

85 | ``` 86 | 87 | Where `now` is a Javascript Date object or a `Date` coercable `Number` or `String`. 88 | 89 | Will output the following 90 | 91 | ```html 92 |

11-05-2016

93 | ``` 94 | 95 | (if the locale is set to 'fr'). 96 | 97 | The method can also accept a second argument of an options object - the options follow the [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat). 98 | 99 | ```html 100 |

101 | ``` 102 | 103 | Will output the following 104 | 105 | ```html 106 |

Mittwoch, 11. Mai 2016

107 | ``` 108 | 109 | (if the locale is set to 'de-DE'). 110 | 111 | Additionally, the method accepts an optional `format` argument that provides some commonly used date formats. 112 | These are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#date-type). 113 | 114 | #### $formatTime 115 | 116 | This will format times to the locale appropriate format. 117 | 118 | 119 | ```html 120 |

121 | ``` 122 | 123 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#time-type). 124 | 125 | Where `now` is a Javascript Date object or a `Date` coercable `Number` or `String`. 126 | 127 | Will output the following 128 | 129 | ```html 130 |

19 h 00

131 | ``` 132 | 133 | (if the locale is set to 'fr'). 134 | 135 | The other options follow the [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat). 136 | 137 | #### $formatRelative 138 | 139 | This will render date-times relative to page load time or to an inserted `now` option to the locale appropriate format. 140 | 141 | 142 | ```html 143 |

144 | ``` 145 | 146 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#time-type). 147 | 148 | Will output the following 149 | 150 | ```html 151 |

2 days ago

152 | ``` 153 | 154 | (if the locale is set to 'en-US'). 155 | 156 | The other options follow the [`Intl.DateTimeFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat). 157 | 158 | #### $formatNumber 159 | 160 | This will set numbers to the locale appropriate format. 161 | 162 | 163 | ```html 164 |

165 | ``` 166 | 167 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#number-type). 168 | 169 | Will output the following 170 | 171 | ```html 172 |

17

173 | ``` 174 | 175 | (if the locale is set to 'en-US'). 176 | 177 | ```html 178 |

179 | ``` 180 | 181 | Will output the following 182 | 183 | ```html 184 |

12%

185 | ``` 186 | 187 | (if the locale is set to 'en-US'). 188 | 189 | The other options follow the [`Intl.NumberFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat). 190 | 191 | #### $formatPlural 192 | 193 | This will format according to plural rules for the locale appropriate format. 194 | 195 | 196 | ```html 197 |

198 | ``` 199 | 200 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#plural-format). 201 | 202 | Will output the following 203 | 204 | ```html 205 |

17

206 | ``` 207 | 208 | (if the locale is set to 'fr-FR'). 209 | 210 | ```html 211 |

212 | ``` 213 | 214 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/#ordinal-format). 215 | 216 | Will output the following 217 | 218 | ```html 219 |

17eme

220 | ``` 221 | 222 | (if the locale is set to 'fr-FR'). 223 | 224 | 225 | #### $formatMessage 226 | 227 | This will translate messages according to the locale appropriate format, it will also apply any pluralization rules 228 | in the message. 229 | 230 | Messages are specified using ICU message syntax. 231 | 232 | 233 | ```html 234 |

240 | ``` 241 | 242 | These formats are described in the [FormatJS main documentation](http://formatjs.io/guides/message-syntax/). 243 | 244 | Will output the following 245 | 246 | ```html 247 |

It's my cat's 7th birthday!

248 | ``` 249 | 250 | (if the locale is set to 'en'). 251 | 252 | #### $formatHTMLMessage 253 | 254 | 255 | Identical to $formatMessage, except that it will escape HTML specific strings to render HTML directly in the message. 256 | 257 | 258 | 259 | #### Loading additional locale data 260 | 261 | By default, only the en specific locale data is included in vue-intl. In order to load locale data for other locales, for example for proper pluralization, ordinals, and relative time formatting, you must load the relevant locale data. Ideally, you would do this dynamically, depending on the locale that is currently in use (as all locale data for all locales is in excess of 1MB). 262 | 263 | To use a specific locale, load the data from the relevant file: 264 | ```js 265 | import esLocaleData from 'vue-intl/locale-data/es'; 266 | import { addLocaleData } from 'vue-intl'; 267 | 268 | addLocaleData(esLocaleData); 269 | ``` 270 | -------------------------------------------------------------------------------- /dist/vue-intl.prod.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.VueIntl=t()}(this,function(){"use strict";var e=Object.prototype.hasOwnProperty;function t(t){var r,n,o,a,i=Array.prototype.slice.call(arguments,1);for(r=0,n=i.length;r1?arguments[1]:{},c={},f={start:Se},p=Se,m=function(e){return{type:"messageFormatPattern",elements:e}},h=c,d=function(e){var t,r,n,o,a,i="";for(t=0,n=e.length;tTe&&(Te=je,Me=[]),Me.push(e))}function Se(){return Ee()}function Ee(){var e,t;for(e=[],t=Ce();t!==c;)e.push(t),t=Ce();return e!==c&&(e=m(e)),e}function Ce(){var e,r,n,o,a,i,l,s,u,f,p,m,$,q,B,J,z,H,W,K,Q,X,Y,ee;return(ee=function(){var e,r,n,o,a,i;if(e=je,r=[],n=je,(o=Ue())!==c&&(a=Be())!==c&&(i=Ue())!==c?n=o=[o,a,i]:(je=n,n=h),n!==c)for(;n!==c;)r.push(n),n=je,(o=Ue())!==c&&(a=Be())!==c&&(i=Ue())!==c?n=o=[o,a,i]:(je=n,n=h);else r=h;return r!==c&&(r=d(r)),(e=r)===c&&(e=je,(r=Ge())!==c&&(r=t.substring(e,je)),e=r),e}())!==c&&(ee=y(ee)),(e=ee)===c&&(r=je,123===t.charCodeAt(je)?(n=_,je++):(n=c,0===De&&Re(w)),n!==c&&Ue()!==c&&(o=function(){var e,r,n;if((e=$e())===c){if(e=je,r=[],v.test(t.charAt(je))?(n=t.charAt(je),je++):(n=c,0===De&&Re(g)),n!==c)for(;n!==c;)r.push(n),v.test(t.charAt(je))?(n=t.charAt(je),je++):(n=c,0===De&&Re(g));else r=h;r!==c&&(r=t.substring(e,je)),e=r}return e}())!==c&&Ue()!==c?(a=je,44===t.charCodeAt(je)?(i=F,je++):(i=c,0===De&&Re(O)),i!==c&&(l=Ue())!==c&&(H=je,t.substr(je,6)===j?(W=j,je+=6):(W=c,0===De&&Re(L)),W===c&&(t.substr(je,4)===P?(W=P,je+=4):(W=c,0===De&&Re(T)),W===c&&(t.substr(je,4)===M?(W=M,je+=4):(W=c,0===De&&Re(D)))),W!==c&&Ue()!==c?(K=je,44===t.charCodeAt(je)?(Q=F,je++):(Q=c,0===De&&Re(O)),Q!==c&&(X=Ue())!==c&&(Y=Be())!==c?K=Q=[Q,X,Y]:(je=K,K=h),K===c&&(K=b),K!==c?(W=R(W,K),H=W):(je=H,H=h)):(je=H,H=h),(u=H)===c&&(q=je,t.substr(je,6)===S?(B=S,je+=6):(B=c,0===De&&Re(E)),B!==c&&Ue()!==c?(44===t.charCodeAt(je)?(J=F,je++):(J=c,0===De&&Re(O)),J!==c&&Ue()!==c&&(z=Ie())!==c?(B=C(z),q=B):(je=q,q=h)):(je=q,q=h),(u=q)===c)&&(f=je,t.substr(je,13)===k?(p=k,je+=13):(p=c,0===De&&Re(I)),p!==c&&Ue()!==c?(44===t.charCodeAt(je)?(m=F,je++):(m=c,0===De&&Re(O)),m!==c&&Ue()!==c&&($=Ie())!==c?(p=G($),f=p):(je=f,f=h)):(je=f,f=h),(u=f)===c)&&(u=function(){var e,r,n,o,a;if(e=je,t.substr(je,6)===U?(r=U,je+=6):(r=c,0===De&&Re(V)),r!==c)if(Ue()!==c)if(44===t.charCodeAt(je)?(n=F,je++):(n=c,0===De&&Re(O)),n!==c)if(Ue()!==c){if(o=[],(a=ke())!==c)for(;a!==c;)o.push(a),a=ke();else o=h;o!==c?(r=Z(o),e=r):(je=e,e=h)}else je=e,e=h;else je=e,e=h;else je=e,e=h;else je=e,e=h;return e}()),(s=u)!==c)?a=i=[i,l,s]:(je=a,a=h),a===c&&(a=b),a!==c&&(i=Ue())!==c?(125===t.charCodeAt(je)?(l=A,je++):(l=c,0===De&&Re(x)),l!==c?(n=N(o,a),r=n):(je=r,r=h)):(je=r,r=h)):(je=r,r=h),e=r),e}function ke(){var e,r,n,o,a,i,l,s,u;return e=je,Ue()!==c?(i=je,l=je,61===t.charCodeAt(je)?(s=$,je++):(s=c,0===De&&Re(q)),s!==c&&(u=$e())!==c?l=s=[s,u]:(je=l,l=h),l!==c&&(l=t.substring(i,je)),(i=l)===c&&(i=Be()),(r=i)!==c&&Ue()!==c?(123===t.charCodeAt(je)?(n=_,je++):(n=c,0===De&&Re(w)),n!==c&&Ue()!==c&&(o=Ee())!==c&&Ue()!==c?(125===t.charCodeAt(je)?(a=A,je++):(a=c,0===De&&Re(x)),a!==c?e=B(r,o):(je=e,e=h)):(je=e,e=h)):(je=e,e=h)):(je=e,e=h),e}function Ie(){var e,r,n,o,a,i,l;if(e=je,a=je,t.substr(je,7)===J?(i=J,je+=7):(i=c,0===De&&Re(z)),i!==c&&Ue()!==c&&(l=$e())!==c?a=i=H(l):(je=a,a=h),(r=a)===c&&(r=b),r!==c)if(Ue()!==c){if(n=[],(o=ke())!==c)for(;o!==c;)n.push(o),o=ke();else n=h;n!==c?e=r=W(r,n):(je=e,e=h)}else je=e,e=h;else je=e,e=h;return e}function Ge(){var e,r;if(De++,e=[],Q.test(t.charAt(je))?(r=t.charAt(je),je++):(r=c,0===De&&Re(X)),r!==c)for(;r!==c;)e.push(r),Q.test(t.charAt(je))?(r=t.charAt(je),je++):(r=c,0===De&&Re(X));else e=h;return De--,e===c&&(r=c,0===De&&Re(K)),e}function Ue(){var e,r,n;for(De++,e=je,r=[],n=Ge();n!==c;)r.push(n),n=Ge();return r!==c&&(r=t.substring(e,je)),De--,(e=r)===c&&(r=c,0===De&&Re(Y)),e}function Ve(){var e;return ee.test(t.charAt(je))?(e=t.charAt(je),je++):(e=c,0===De&&Re(te)),e}function Ze(){var e;return re.test(t.charAt(je))?(e=t.charAt(je),je++):(e=c,0===De&&Re(ne)),e}function $e(){var e,r,n,o,a;if(48===t.charCodeAt(je)?(e=oe,je++):(e=c,0===De&&Re(ae)),e===c){if(e=je,r=je,ie.test(t.charAt(je))?(n=t.charAt(je),je++):(n=c,0===De&&Re(le)),n!==c){for(o=[],a=Ve();a!==c;)o.push(a),a=Ve();o!==c?r=n=[n,o]:(je=r,r=h)}else je=r,r=h;r!==c&&(r=t.substring(e,je)),e=r}return e!==c&&(e=se(e)),e}function qe(){var e,r,n,o,a,i,l,s;return ue.test(t.charAt(je))?(e=t.charAt(je),je++):(e=c,0===De&&Re(ce)),e===c&&(e=je,t.substr(je,2)===fe?(r=fe,je+=2):(r=c,0===De&&Re(pe)),r!==c&&(r=me()),(e=r)===c&&(e=je,t.substr(je,2)===he?(r=he,je+=2):(r=c,0===De&&Re(de)),r!==c&&(r=ye()),(e=r)===c&&(e=je,t.substr(je,2)===ve?(r=ve,je+=2):(r=c,0===De&&Re(ge)),r!==c&&(r=_e()),(e=r)===c&&(e=je,t.substr(je,2)===we?(r=we,je+=2):(r=c,0===De&&Re(be)),r!==c&&(r=Fe()),(e=r)===c&&(e=je,t.substr(je,2)===Oe?(r=Oe,je+=2):(r=c,0===De&&Re(Ae)),r!==c?(n=je,o=je,(a=Ze())!==c&&(i=Ze())!==c&&(l=Ze())!==c&&(s=Ze())!==c?o=a=[a,i,l,s]:(je=o,o=h),o!==c&&(o=t.substring(n,je)),(n=o)!==c?e=r=xe(n):(je=e,e=h)):(je=e,e=h)))))),e}function Be(){var e,t;if(e=[],(t=qe())!==c)for(;t!==c;)e.push(t),t=qe();else e=h;return e!==c&&(e=Ne(e)),e}if((r=p())!==c&&je===t.length)return r;throw r!==c&&jei&&(Le=0,Pe={line:1,column:1,seenCR:!1}),function(e,r,n){var o,a;for(o=r;ot.description?1:0});t1?n.slice(0,-1).join(", ")+" or "+n[e.length-1]:n[0])+" but "+(t?'"'+function(e){function t(e){return e.charCodeAt(0).toString(16).toUpperCase()}return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E\x0F]/g,function(e){return"\\x0"+t(e)}).replace(/[\x10-\x1F\x80-\xFF]/g,function(e){return"\\x"+t(e)}).replace(/[\u0180-\u0FFF]/g,function(e){return"\\u0"+t(e)}).replace(/[\u1080-\uFFFF]/g,function(e){return"\\u"+t(e)})}(t)+'"':"end of input")+" found."}(o,s),o,s,a,l.line,l.column)}}}();function c(e,t,n){var o="string"==typeof e?c.__parse(e):e;if(!o||"messageFormatPattern"!==o.type)throw new TypeError("A message must be provided as a String or AST.");n=this._mergeFormats(c.formats,n),r(this,"_locale",{value:this._resolveLocale(t)});var a=this._findPluralRuleFunction(this._locale),i=this._compilePattern(o,t,n,a),l=this;this.format=function(e){return l._format(i,e)}}r(c,"formats",{enumerable:!0,value:{number:{currency:{style:"currency"},percent:{style:"percent"}},date:{short:{month:"numeric",day:"numeric",year:"2-digit"},medium:{month:"short",day:"numeric",year:"numeric"},long:{month:"long",day:"numeric",year:"numeric"},full:{weekday:"long",month:"long",day:"numeric",year:"numeric"}},time:{short:{hour:"numeric",minute:"numeric"},medium:{hour:"numeric",minute:"numeric",second:"numeric"},long:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"},full:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"}}}}),r(c,"__localeData__",{value:n(null)}),r(c,"__addLocaleData",{value:function(e){if(!e||!e.locale)throw new Error("Locale data provided to IntlMessageFormat is missing a `locale` property");c.__localeData__[e.locale.toLowerCase()]=e}}),r(c,"__parse",{value:u.parse}),r(c,"defaultLocale",{enumerable:!0,writable:!0,value:void 0}),c.prototype.resolvedOptions=function(){return{locale:this._locale}},c.prototype._compilePattern=function(e,t,r,n){return new o(t,r,n).compile(e)},c.prototype._findPluralRuleFunction=function(e){for(var t=c.__localeData__,r=t[e.toLowerCase()];r;){if(r.pluralRuleFunction)return r.pluralRuleFunction;r=r.parentLocale&&t[r.parentLocale.toLowerCase()]}throw new Error("Locale data added to IntlMessageFormat is missing a `pluralRuleFunction` for :"+e)},c.prototype._format=function(t,r){var n,o,a,i,l,s="";for(n=0,o=t.length;n=0)return!0;if("string"==typeof e){var t=/s$/.test(e)&&e.substr(0,e.length-1);if(t&&v.call(w,t)>=0)throw new Error('"'+e+'" is not a valid IntlRelativeFormat `units` value, did you mean: '+t)}throw new Error('"'+e+'" is not a valid IntlRelativeFormat `units` value, it must be one of: "'+w.join('", "')+'"')},F.prototype._resolveLocale=function(e){"string"==typeof e&&(e=[e]),e=(e||[]).concat(F.defaultLocale);var t,r,n,o,a=F.__localeData__;for(t=0,r=e.length;t=0)return e;throw new Error('"'+e+'" is not a valid IntlRelativeFormat `style` value, it must be one of: "'+b.join('", "')+'"')},F.prototype._selectUnits=function(e){var t,r,n;for(t=0,r=w.length;t0&&void 0!==arguments[0]?arguments[0]:[];(Array.isArray(e)?e:[e]).forEach(function(e){e&&e.locale&&(c.__addLocaleData(e),F.__addLocaleData(e))})}F.__addLocaleData(O),F.defaultLocale="en";!function(){function e(e){this.value=e}function t(t){var r,n;function o(r,n){try{var i=t[r](n),l=i.value;l instanceof e?Promise.resolve(l.value).then(function(e){o("next",e)},function(e){o("throw",e)}):a(i.done?"return":"normal",i.value)}catch(e){a("throw",e)}}function a(e,t){switch(e){case"return":r.resolve({value:t,done:!0});break;case"throw":r.reject(t);break;default:r.resolve({value:t,done:!1})}(r=r.next)?o(r.key,r.arg):n=null}this._invoke=function(e,t){return new Promise(function(a,i){var l={key:e,arg:t,resolve:a,reject:i,next:null};n?n=n.next=l:(r=n=l,o(e,t))})},"function"!=typeof t.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(t.prototype[Symbol.asyncIterator]=function(){return this}),t.prototype.next=function(e){return this._invoke("next",e)},t.prototype.throw=function(e){return this._invoke("throw",e)},t.prototype.return=function(e){return this._invoke("return",e)}}();var x=Function.prototype.bind||function(e){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var t=Array.prototype.slice.call(arguments,1),r=this,n=function(){},o=function(){return r.apply(this instanceof n?this:e,t.concat(Array.prototype.slice.call(arguments)))};return this.prototype&&(n.prototype=this.prototype),o.prototype=new n,o},N=Object.prototype.hasOwnProperty,j=function(){try{return!!Object.defineProperty({},"a",{})}catch(e){return!1}}()?Object.defineProperty:function(e,t,r){"get"in r&&e.__defineGetter__?e.__defineGetter__(t,r.get):(!N.call(e,t)||"value"in r)&&(e[t]=r.value)},L=Object.create||function(e,t){var r,n;function o(){}for(n in o.prototype=e,r=new o,t)N.call(t,n)&&j(r,n,t[n]);return r};function P(e){var t=L(null);return function(){var r=Array.prototype.slice.call(arguments),n=function(e){if("undefined"==typeof JSON)return;var t,r,n,o=[];for(t=0,r=e.length;t1&&void 0!==arguments[1]?arguments[1]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e);var n,o,a="ordinal"===r.style,i=(o=t,n=c.prototype._resolveLocale(o),c.prototype._findPluralRuleFunction(n));this.format=function(e){return i(e,a)}}),now:Date.now},D={type:Boolean},R={type:Number},S={type:String},E=function(e){return{validator:function(t){return e.indexOf(t)>-1}}},C={localeMatcher:E(["best fit","lookup"]),formatMatcher:E(["basic","best fit"]),timeZone:S,hour12:D,weekday:E(["narrow","short","long"]),era:E(["narrow","short","long"]),year:E(["numeric","2-digit"]),month:E(["numeric","2-digit","narrow","short","long"]),day:E(["numeric","2-digit"]),hour:E(["numeric","2-digit"]),minute:E(["numeric","2-digit"]),second:E(["numeric","2-digit"]),timeZoneName:E(["short","long"])},k={localeMatcher:E(["best fit","lookup"]),style:E(["decimal","currency","percent"]),currency:S,currencyDisplay:E(["symbol","code","name"]),useGrouping:D,minimumIntegerDigits:R,minimumFractionDigits:R,maximumFractionDigits:R,minimumSignificantDigits:R,maximumSignificantDigits:R},I={style:E(["best fit","numeric"]),units:E(["second","minute","hour","day","month","year"])},G={style:E(["cardinal","ordinal"])},U={"&":"&",">":">","<":"<",'"':""","'":"'"},V=/[&><"']/g;function Z(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return t.reduce(function(t,n){return e.hasOwnProperty(n)?t[n]=e[n]:r.hasOwnProperty(n)&&(t[n]=r[n]),t},{})}var $=Object.keys(C),q=Object.keys(k),B=Object.keys(I),J=Object.keys(G),z={second:60,minute:60,hour:24,day:30,month:12};function H(e){var t=F.thresholds;t.second=e.second,t.minute=e.minute,t.hour=e.hour,t.day=e.day,t.month=e.month}function W(e,t,r){var n=e&&e[t]&&e[t][r];if(n)return n}function K(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=e.formats,i=e.messages,l=e.defaultLocale,s=e.defaultFormats,u=r.id,c=r.defaultMessage;if(!u)throw new TypeError("[Vue Intl] An `id` must be provided to format a message.");var f=i&&function(e,t){var r=void 0;try{var n=t.split(".").reduce(function(e,t){return e&&e[t]},e);if("string"!=typeof n)throw new Error("Path not found");r=n}catch(n){r=e[t]}return r}(i,u);if(!(Object.keys(n).length>0))return f||c||u;var p=void 0;if(f)try{p=t.getMessageFormat(f,o,a).format(n)}catch(e){}if(!p&&c)try{p=t.getMessageFormat(c,l,s).format(n)}catch(e){}return p||f||c||u}var Q=Object.freeze({formatDate:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=e.formats,i=n.format,l=new Date(r),s=i&&W(a,"date",i),u=Z(n,$,s);try{return t.getDateTimeFormat(o,u).format(l)}catch(e){}return String(l)},formatTime:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=e.formats,i=n.format,l=new Date(r),s=i&&W(a,"time",i),u=Z(n,$,s);0===Object.keys(u).length&&(u={hour:"numeric",minute:"numeric"});try{return t.getDateTimeFormat(o,u).format(l)}catch(e){}return String(l)},formatRelative:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=e.formats,i=n.format,l=new Date(r),s=new Date(n.now),u=i&&W(a,"relative",i),c=Z(n,B,u),f=Object.assign({},F.thresholds);H(z);try{return t.getRelativeFormat(o,c).format(l,{now:isFinite(s)?s:t.now()})}catch(e){}finally{H(f)}return String(l)},formatNumber:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=e.formats,i=n.format,l=i&&W(a,"number",i),s=Z(n,q,l);try{return t.getNumberFormat(o,s).format(r)}catch(e){}return String(r)},formatPlural:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=e.locale,a=Z(n,J);try{return t.getPluralFormat(o,a).format(r)}catch(e){}return"other"},formatMessage:K,formatHTMLMessage:function(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return K(e,t,r,Object.keys(n).reduce(function(e,t){var r=n[t];return e[t]="string"==typeof r?(""+r).replace(V,function(e){return U[e]}):r,e},{}))}}),X=function(e,t){return e.parent.$formatMessage(e.props,t)},Y=/@\0@\0(.*?)\0@\0@/g,ee={functional:!0,props:{id:{type:String,required:!0},defaultMessage:String,values:Object,tagName:{type:String,default:"span"}},render:function(e,t){var r=t.slots(),n=Object.keys(r);if(0===n.length)return e(t.props.tagName,t.data,X(t,t.props.values));var o=Object.assign({},t.props.values);n.forEach(function(e){o[e]="@\0@\0"+e+"\0@\0@"});for(var a=X(t,o),i=void 0,l=0,s=[];null!==(i=Y.exec(a));)s.push(a.substring(l,i.index),r[i[1]]),l=i.index+i[0].length;return s.push(a.substring(l)),e(t.props.tagName,t.data,s)}};return A({locale:"en",pluralRuleFunction:function(e,t){var r=String(e).split("."),n=!r[1],o=Number(r[0])==e,a=o&&r[0].slice(-1),i=o&&r[0].slice(-2);return t?1==a&&11!=i?"one":2==a&&12!=i?"two":3==a&&13!=i?"few":"other":1==e&&n?"one":"other"},fields:{year:{displayName:"year",relative:{0:"this year",1:"next year","-1":"last year"},relativeTime:{future:{one:"in {0} year",other:"in {0} years"},past:{one:"{0} year ago",other:"{0} years ago"}}},"year-short":{displayName:"yr.",relative:{0:"this yr.",1:"next yr.","-1":"last yr."},relativeTime:{future:{one:"in {0} yr.",other:"in {0} yr."},past:{one:"{0} yr. ago",other:"{0} yr. ago"}}},month:{displayName:"month",relative:{0:"this month",1:"next month","-1":"last month"},relativeTime:{future:{one:"in {0} month",other:"in {0} months"},past:{one:"{0} month ago",other:"{0} months ago"}}},"month-short":{displayName:"mo.",relative:{0:"this mo.",1:"next mo.","-1":"last mo."},relativeTime:{future:{one:"in {0} mo.",other:"in {0} mo."},past:{one:"{0} mo. ago",other:"{0} mo. ago"}}},day:{displayName:"day",relative:{0:"today",1:"tomorrow","-1":"yesterday"},relativeTime:{future:{one:"in {0} day",other:"in {0} days"},past:{one:"{0} day ago",other:"{0} days ago"}}},"day-short":{displayName:"day",relative:{0:"today",1:"tomorrow","-1":"yesterday"},relativeTime:{future:{one:"in {0} day",other:"in {0} days"},past:{one:"{0} day ago",other:"{0} days ago"}}},hour:{displayName:"hour",relative:{0:"this hour"},relativeTime:{future:{one:"in {0} hour",other:"in {0} hours"},past:{one:"{0} hour ago",other:"{0} hours ago"}}},"hour-short":{displayName:"hr.",relative:{0:"this hour"},relativeTime:{future:{one:"in {0} hr.",other:"in {0} hr."},past:{one:"{0} hr. ago",other:"{0} hr. ago"}}},minute:{displayName:"minute",relative:{0:"this minute"},relativeTime:{future:{one:"in {0} minute",other:"in {0} minutes"},past:{one:"{0} minute ago",other:"{0} minutes ago"}}},"minute-short":{displayName:"min.",relative:{0:"this minute"},relativeTime:{future:{one:"in {0} min.",other:"in {0} min."},past:{one:"{0} min. ago",other:"{0} min. ago"}}},second:{displayName:"second",relative:{0:"now"},relativeTime:{future:{one:"in {0} second",other:"in {0} seconds"},past:{one:"{0} second ago",other:"{0} seconds ago"}}},"second-short":{displayName:"sec.",relative:{0:"now"},relativeTime:{future:{one:"in {0} sec.",other:"in {0} sec."},past:{one:"{0} sec. ago",other:"{0} sec. ago"}}}}}),{addLocaleData:A,FormattedMessage:ee,install:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e.addLocaleData=A,e.registerMessages=function(e,t,r){e.__allMessages=e.__allMessages||{},e.__allMessages[t]=e.__allMessages[t]||{},Object.assign(e.__allMessages[t],r)}.bind(null,e),e.registerFormats=function(e,t,r){e.__allFormats=e.__allFormats||{},e.__allFormats[t]=e.__allFormats[t]||{},Object.assign(e.__allFormats[t],r)}.bind(null,e),e.setLocale=function(e,t){e.set(e,"locale",t);var r=Object.getOwnPropertyNames((e.__allFormats||{})[t]||{}).length>0;e.__format_config.formats=r?e.__allFormats[t]:e.__format_config.defaultFormats,e.__format_config.messages=(e.__allMessages||{})[t]||{}}.bind(null,e),e.__format_state=M,e.__format_config={defaultLocale:t.defaultLocale||"en",defaultFormats:t.defaultFormats||{}};var r=!0,n=!1,o=void 0;try{for(var a,i=function(){var t=a.value;e.prototype["$"+t]=function(){var r,n,o={locale:e.locale};Object.assign(o,(n=(r=e).locale,{formats:Object.getOwnPropertyNames((r.__allFormats||{})[n]||{}).length>0?r.__allFormats[n]:r.__format_config.defaultFormats,messages:(r.__allMessages||{})[n]||{},defaultLocale:r.__format_config.defaultLocale,defaultFormats:r.__format_config.defaultFormats}));for(var a=e.__format_state,i=arguments.length,l=Array(i),s=0;s=6.0" 15 | }, 16 | "scripts": { 17 | "clean": "rimraf coverage/ dist/", 18 | "test:code": "NODE_ENV=test mocha", 19 | "test:code-cov": "NODE_ENV=test babel-node ./node_modules/istanbul/lib/cli cover --report lcovonly ./node_modules/.bin/_mocha", 20 | "lint": "eslint .", 21 | "test": "npm run test:code && npm run lint", 22 | "test:cov": "npm run lint && npm run test:code-cov", 23 | "build:data": "babel-node scripts/build-data", 24 | "build:dev": "NODE_ENV=development rollup -c rollup.config.dist.js", 25 | "build:prod": "NODE_ENV=production rollup -c rollup.config.dist.js", 26 | "build": "npm run build:data && npm run build:dev && npm run build:prod", 27 | "prepublish": "npm run clean && npm run build" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/learningequality/vue-intl.git" 32 | }, 33 | "keywords": [ 34 | "intl", 35 | "i18n", 36 | "internationalization", 37 | "locale", 38 | "localization", 39 | "globalization", 40 | "vue", 41 | "vuejs", 42 | "format", 43 | "formatting", 44 | "translate", 45 | "translation" 46 | ], 47 | "author": "Richard Tibbles ", 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/learningequality/vue-intl/issues" 51 | }, 52 | "homepage": "https://github.com/learningequality/vue-intl#readme", 53 | "dependencies": { 54 | "babel-runtime": "^6.26.0", 55 | "intl-format-cache": "^2.0.5", 56 | "intl-messageformat": "^1.3.0", 57 | "intl-relativeformat": "^1.3.0" 58 | }, 59 | "peerDependencies": { 60 | "vue": "^2.0.0" 61 | }, 62 | "devDependencies": { 63 | "babel-cli": "^6.2.0", 64 | "babel-eslint": "^7.1.1", 65 | "babel-plugin-external-helpers": "^6.18.0", 66 | "babel-plugin-transform-async-to-generator": "^6.16.0", 67 | "babel-plugin-transform-class-properties": "^6.11.5", 68 | "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", 69 | "babel-plugin-transform-es3-member-expression-literals": "^6.3.13", 70 | "babel-plugin-transform-es3-property-literals": "^6.3.13", 71 | "babel-plugin-transform-object-rest-spread": "^6.1.18", 72 | "babel-preset-es2015": "^6.1.18", 73 | "eslint": "^2.9.0", 74 | "expect": "^1.19.0", 75 | "formatjs-extract-cldr-data": "^4.0.0", 76 | "intl": "^1.1.0", 77 | "istanbul": "^1.0.0-alpha.2", 78 | "mkdirp": "^0.5.1", 79 | "mocha": "^2.4.5", 80 | "rimraf": "^2.5.2", 81 | "rollup": "^0.50.0", 82 | "rollup-plugin-babel": "^3.0.2", 83 | "rollup-plugin-commonjs": "^8.2.1", 84 | "rollup-plugin-memory": "^2.0.0", 85 | "rollup-plugin-node-resolve": "^3.0.0", 86 | "rollup-plugin-replace": "^2.0.0", 87 | "rollup-plugin-uglify": "^2.0.0", 88 | "serialize-javascript": "^1.1.1" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /rollup.config.dist.js: -------------------------------------------------------------------------------- 1 | import * as p from 'path'; 2 | import babel from 'rollup-plugin-babel'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import commonjs from 'rollup-plugin-commonjs'; 5 | import replace from 'rollup-plugin-replace'; 6 | import uglify from 'rollup-plugin-uglify'; 7 | 8 | const copyright = ( 9 | ` 10 | /* 11 | * This software is distributed under an MIT license, see accompanying LICENSE file for details. 12 | */ 13 | 14 | /* 15 | * Many files in this package are modified versions of those used in the react-intl package. 16 | * https://github.com/yahoo/react-intl 17 | * The license notice below is provided to comply with the terms of the BSD license of that package. 18 | */ 19 | 20 | /* 21 | * Copyright 2015, Yahoo Inc. 22 | * Copyrights licensed under the New BSD License. 23 | * Redistribution and use in source and binary forms, with or without 24 | * modification, are permitted provided that the following conditions are met: 25 | * 26 | * * Redistributions of source code must retain the above copyright 27 | * notice, this list of conditions and the following disclaimer. 28 | * 29 | * * Redistributions in binary form must reproduce the above copyright 30 | * notice, this list of conditions and the following disclaimer in the 31 | * documentation and/or other materials provided with the distribution. 32 | * 33 | * * Neither the name of the Yahoo Inc. nor the 34 | * names of its contributors may be used to endorse or promote products 35 | * derived from this software without specific prior written permission. 36 | * 37 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 38 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 39 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 40 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 41 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 42 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 43 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 44 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 46 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | */ 48 | ` 49 | ); 50 | 51 | const isProduction = process.env.NODE_ENV === 'production'; 52 | 53 | export default { 54 | input: p.resolve('src/vue-intl.js'), 55 | output: { 56 | file: p.resolve(`dist/vue-intl.${isProduction ? 'prod.min.js' : 'dev.js'}`), 57 | format: 'umd', 58 | }, 59 | name: 'VueIntl', 60 | banner: copyright, 61 | sourcemap: true, 62 | plugins: [ 63 | babel(), 64 | nodeResolve({ 65 | jsnext: true, 66 | }), 67 | commonjs({ 68 | sourcemap: true, 69 | }), 70 | replace({ 71 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 72 | }), 73 | isProduction && 74 | uglify({ 75 | warnings: false, 76 | }), 77 | ].filter(Boolean), 78 | }; 79 | -------------------------------------------------------------------------------- /scripts/.babelrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "presets": [ 4 | "es2015" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/build-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This software is distributed under an MIT license, see accompanying LICENSE file for details. 3 | */ 4 | 5 | /* 6 | * Many files in this package are modified versions of those used in the react-intl package. 7 | * https://github.com/yahoo/react-intl 8 | * The license notice below is provided to comply with the terms of the BSD license of that package. 9 | */ 10 | 11 | /* 12 | * Copyright 2015, Yahoo Inc. 13 | * Copyrights licensed under the New BSD License. 14 | * Redistribution and use in source and binary forms, with or without 15 | * modification, are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright 18 | * notice, this list of conditions and the following disclaimer. 19 | * 20 | * * Redistributions in binary form must reproduce the above copyright 21 | * notice, this list of conditions and the following disclaimer in the 22 | * documentation and/or other materials provided with the distribution. 23 | * 24 | * * Neither the name of the Yahoo Inc. nor the 25 | * names of its contributors may be used to endorse or promote products 26 | * derived from this software without specific prior written permission. 27 | * 28 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 29 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 30 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 31 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 32 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 33 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 34 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 35 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 37 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | */ 39 | 40 | import * as fs from 'fs'; 41 | import * as p from 'path'; 42 | import {sync as mkdirpSync} from 'mkdirp'; 43 | import extractCLDRData from 'formatjs-extract-cldr-data'; 44 | import serialize from 'serialize-javascript'; 45 | import {rollup} from 'rollup'; 46 | import memory from 'rollup-plugin-memory'; 47 | import uglify from 'rollup-plugin-uglify'; 48 | 49 | const DEFAULT_LOCALE = 'en'; 50 | 51 | const cldrData = extractCLDRData({ 52 | pluralRules: true, 53 | relativeFields: true, 54 | }); 55 | 56 | const cldrDataByLocale = new Map( 57 | Object.keys(cldrData).map(locale => [locale, cldrData[locale]]) 58 | ); 59 | 60 | const cldrDataByLang = [...cldrDataByLocale].reduce((map, [locale, data]) => { 61 | const [lang] = locale.split('-'); 62 | const langData = map.get(lang) || []; 63 | return map.set(lang, langData.concat(data)); 64 | }, new Map()); 65 | 66 | function createDataModule(localeData) { 67 | return `// GENERATED FILE 68 | export default ${serialize(localeData)}; 69 | `; 70 | } 71 | 72 | function writeUMDFile(filename, module) { 73 | const lang = p.basename(filename, '.js'); 74 | 75 | return rollup({ 76 | input: filename, 77 | plugins: [ 78 | memory({ 79 | path: filename, 80 | contents: module, 81 | }), 82 | uglify(), 83 | ], 84 | }) 85 | .then(bundle => { 86 | return bundle.write({ 87 | file: filename, 88 | format: 'umd', 89 | name: `VueIntlLocaleData.${lang}`, 90 | }); 91 | }) 92 | .then(() => filename); 93 | } 94 | 95 | function writeFile(filename, contents) { 96 | return new Promise((resolve, reject) => { 97 | fs.writeFile(filename, contents, err => { 98 | if (err) { 99 | reject(err); 100 | } else { 101 | resolve(p.resolve(filename)); 102 | } 103 | }); 104 | }); 105 | } 106 | 107 | // ----------------------------------------------------------------------------- 108 | 109 | mkdirpSync('locale-data/'); 110 | 111 | const defaultData = createDataModule(cldrDataByLocale.get(DEFAULT_LOCALE)); 112 | writeFile(`src/${DEFAULT_LOCALE}.js`, defaultData); 113 | 114 | const allData = createDataModule([...cldrDataByLocale.values()]); 115 | writeUMDFile('locale-data/index.js', allData); 116 | 117 | cldrDataByLang.forEach((cldrData, lang) => { 118 | writeUMDFile(`locale-data/${lang}.js`, createDataModule(cldrData)); 119 | }); 120 | 121 | process.on('unhandledRejection', reason => { 122 | throw reason; 123 | }); 124 | console.log('> Writing locale data files...'); 125 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { 4 | "modules": false 5 | }], 6 | ], 7 | "plugins": [ 8 | "transform-object-rest-spread", 9 | "transform-class-properties", 10 | "transform-es3-member-expression-literals", 11 | "transform-es3-property-literals" 12 | ], 13 | "env": { 14 | "development": { 15 | "plugins": [ 16 | "external-helpers" 17 | ] 18 | }, 19 | "production": { 20 | "plugins": [ 21 | "external-helpers" 22 | ] 23 | }, 24 | "test": { 25 | "plugins": [ 26 | "transform-es2015-modules-commonjs" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/FormattedMessage.js: -------------------------------------------------------------------------------- 1 | const format = (ctx, values) => ctx.parent.$formatMessage(ctx.props, values) 2 | const placeholder = (name) => `@\0@\0${name}\0@\0@` 3 | const placeholderRegex = /@\0@\0(.*?)\0@\0@/g 4 | 5 | export default { 6 | functional: true, 7 | 8 | props: { 9 | id: {type: String, required: true}, 10 | defaultMessage: String, 11 | values: Object, 12 | tagName: {type: String, default: 'span'} 13 | }, 14 | 15 | render (h, ctx) { 16 | const slots = ctx.slots() 17 | const slotNames = Object.keys(slots) 18 | if (slotNames.length === 0) { 19 | return h(ctx.props.tagName, ctx.data, format(ctx, ctx.props.values)) 20 | } 21 | 22 | const values = Object.assign({}, ctx.props.values) 23 | slotNames.forEach(slot => { 24 | values[slot] = placeholder(slot) 25 | }) 26 | 27 | const message = format(ctx, values) 28 | 29 | let match; 30 | let pos = 0; 31 | const children = [] 32 | while ((match = placeholderRegex.exec(message)) !== null) { 33 | children.push(message.substring(pos, match.index), slots[match[1]]) 34 | pos = match.index + match[0].length 35 | } 36 | children.push(message.substring(pos)) 37 | return h(ctx.props.tagName, ctx.data, children) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/format.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a modified version of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | import IntlRelativeFormat from 'intl-relativeformat'; 37 | 38 | import { 39 | dateTimeFormatPropTypes, 40 | numberFormatPropTypes, 41 | relativeFormatPropTypes, 42 | pluralFormatPropTypes 43 | } from './types'; 44 | 45 | import { 46 | escape, 47 | filterProps, 48 | getMessage 49 | } from './utils'; 50 | 51 | const DATE_TIME_FORMAT_OPTIONS = Object.keys(dateTimeFormatPropTypes); 52 | const NUMBER_FORMAT_OPTIONS = Object.keys(numberFormatPropTypes); 53 | const RELATIVE_FORMAT_OPTIONS = Object.keys(relativeFormatPropTypes); 54 | const PLURAL_FORMAT_OPTIONS = Object.keys(pluralFormatPropTypes); 55 | 56 | const RELATIVE_FORMAT_THRESHOLDS = { 57 | second: 60, // seconds to minute 58 | minute: 60, // minutes to hour 59 | hour : 24, // hours to day 60 | day : 30, // days to month 61 | month : 12 // months to year 62 | }; 63 | 64 | function updateRelativeFormatThresholds(newThresholds) { 65 | const {thresholds} = IntlRelativeFormat; 66 | ({ 67 | second: thresholds.second, 68 | minute: thresholds.minute, 69 | hour : thresholds.hour, 70 | day : thresholds.day, 71 | month : thresholds.month 72 | } = newThresholds); 73 | } 74 | 75 | function getNamedFormat(formats, type, name) { 76 | let format = formats && formats[type] && formats[type][name]; 77 | if (format) { 78 | return format; 79 | } 80 | 81 | if (process.env.NODE_ENV !== 'production') { 82 | console.error( 83 | `[Vue Intl] No ${type} format named: ${name}` 84 | ); 85 | } 86 | } 87 | 88 | export function formatDate(config, state, value, options = {}) { 89 | const {locale, formats} = config; 90 | const {format} = options; 91 | 92 | let date = new Date(value); 93 | let defaults = format && getNamedFormat(formats, 'date', format); 94 | let filteredOptions = filterProps(options, DATE_TIME_FORMAT_OPTIONS, defaults); 95 | 96 | try { 97 | return state.getDateTimeFormat(locale, filteredOptions).format(date); 98 | } catch (e) { 99 | if (process.env.NODE_ENV !== 'production') { 100 | console.error( 101 | `[Vue Intl] Error formatting date.\n${e}` 102 | ); 103 | } 104 | } 105 | 106 | return String(date); 107 | } 108 | 109 | export function formatTime(config, state, value, options = {}) { 110 | const {locale, formats} = config; 111 | const {format} = options; 112 | 113 | let date = new Date(value); 114 | let defaults = format && getNamedFormat(formats, 'time', format); 115 | let filteredOptions = filterProps(options, DATE_TIME_FORMAT_OPTIONS, defaults); 116 | 117 | // When no formatting options have been specified, default to outputting a 118 | // time; e.g.: "9:42 AM". 119 | if (Object.keys(filteredOptions).length === 0) { 120 | filteredOptions = { 121 | hour : 'numeric', 122 | minute: 'numeric' 123 | }; 124 | } 125 | 126 | try { 127 | return state.getDateTimeFormat(locale, filteredOptions).format(date); 128 | } catch (e) { 129 | if (process.env.NODE_ENV !== 'production') { 130 | console.error( 131 | `[Vue Intl] Error formatting time.\n${e}` 132 | ); 133 | } 134 | } 135 | 136 | return String(date); 137 | } 138 | 139 | export function formatRelative(config, state, value, options = {}) { 140 | const {locale, formats} = config; 141 | const {format} = options; 142 | 143 | let date = new Date(value); 144 | let now = new Date(options.now); 145 | let defaults = format && getNamedFormat(formats, 'relative', format); 146 | let filteredOptions = filterProps(options, RELATIVE_FORMAT_OPTIONS, defaults); 147 | 148 | // Capture the current threshold values, then temporarily override them with 149 | // specific values just for this render. 150 | const oldThresholds = Object.assign({}, IntlRelativeFormat.thresholds); 151 | updateRelativeFormatThresholds(RELATIVE_FORMAT_THRESHOLDS); 152 | 153 | try { 154 | return state.getRelativeFormat(locale, filteredOptions).format(date, { 155 | now: isFinite(now) ? now : state.now() 156 | }); 157 | } catch (e) { 158 | if (process.env.NODE_ENV !== 'production') { 159 | console.error( 160 | `[Vue Intl] Error formatting relative time.\n${e}` 161 | ); 162 | } 163 | } finally { 164 | updateRelativeFormatThresholds(oldThresholds); 165 | } 166 | 167 | return String(date); 168 | } 169 | 170 | export function formatNumber(config, state, value, options = {}) { 171 | const {locale, formats} = config; 172 | const {format} = options; 173 | 174 | let defaults = format && getNamedFormat(formats, 'number', format); 175 | let filteredOptions = filterProps(options, NUMBER_FORMAT_OPTIONS, defaults); 176 | 177 | try { 178 | return state.getNumberFormat(locale, filteredOptions).format(value); 179 | } catch (e) { 180 | if (process.env.NODE_ENV !== 'production') { 181 | console.error( 182 | `[Vue Intl] Error formatting number.\n${e}` 183 | ); 184 | } 185 | } 186 | 187 | return String(value); 188 | } 189 | 190 | export function formatPlural(config, state, value, options = {}) { 191 | const {locale} = config; 192 | 193 | let filteredOptions = filterProps(options, PLURAL_FORMAT_OPTIONS); 194 | 195 | try { 196 | return state.getPluralFormat(locale, filteredOptions).format(value); 197 | } catch (e) { 198 | if (process.env.NODE_ENV !== 'production') { 199 | console.error( 200 | `[Vue Intl] Error formatting plural.\n${e}` 201 | ); 202 | } 203 | } 204 | 205 | return 'other'; 206 | } 207 | 208 | export function formatMessage(config, state, messageDescriptor = {}, values = {}) { 209 | const { 210 | locale, 211 | formats, 212 | messages, 213 | defaultLocale, 214 | defaultFormats 215 | } = config; 216 | 217 | const { 218 | id, 219 | defaultMessage 220 | } = messageDescriptor; 221 | 222 | // `id` is a required field of a Message Descriptor. 223 | if (!id) { 224 | throw new TypeError('[Vue Intl] An `id` must be provided to format a message.'); 225 | } 226 | 227 | const message = messages && getMessage(messages, id); 228 | const hasValues = Object.keys(values).length > 0; 229 | 230 | // Avoid expensive message formatting for simple messages without values. In 231 | // development messages will always be formatted in case of missing values. 232 | if (!hasValues && process.env.NODE_ENV === 'production') { 233 | return message || defaultMessage || id; 234 | } 235 | 236 | // When debugging in English, show default string to enable hot module reloading 237 | if (process.env.NODE_ENV === 'development' && defaultMessage && locale === 'en') { 238 | var formatter = state.getMessageFormat(defaultMessage, locale, formats); 239 | return formatter.format(values); 240 | } 241 | 242 | let formattedMessage; 243 | 244 | if (message) { 245 | try { 246 | let formatter = state.getMessageFormat( 247 | message, locale, formats 248 | ); 249 | 250 | formattedMessage = formatter.format(values); 251 | } catch (e) { 252 | if (process.env.NODE_ENV !== 'production') { 253 | console.error( 254 | `[Vue Intl] Error formatting message: "${id}" for locale: "${locale}"` + 255 | (defaultMessage ? ', using default message as fallback.' : '') + 256 | `\n${e}` 257 | ); 258 | } 259 | } 260 | } else { 261 | if (process.env.NODE_ENV !== 'production') { 262 | // This prevents warnings from littering the console in development 263 | // when no `messages` are passed into the for the 264 | // default locale, and a default message is in the source. 265 | if (!defaultMessage || 266 | (locale && locale.toLowerCase() !== defaultLocale.toLowerCase())) { 267 | 268 | console.error( 269 | `[Vue Intl] Missing message: "${id}" for locale: "${locale}"` + 270 | (defaultMessage ? ', using default message as fallback.' : '') 271 | ); 272 | } 273 | } 274 | } 275 | 276 | if (!formattedMessage && defaultMessage) { 277 | try { 278 | let formatter = state.getMessageFormat( 279 | defaultMessage, defaultLocale, defaultFormats 280 | ); 281 | 282 | formattedMessage = formatter.format(values); 283 | } catch (e) { 284 | if (process.env.NODE_ENV !== 'production') { 285 | console.error( 286 | `[Vue Intl] Error formatting the default message for: "${id}"` + 287 | `\n${e}` 288 | ); 289 | } 290 | } 291 | } 292 | 293 | if (!formattedMessage) { 294 | if (process.env.NODE_ENV !== 'production') { 295 | console.error( 296 | `[Vue Intl] Cannot format message: "${id}", ` + 297 | `using message ${message || defaultMessage ? 'source' : 'id'} as fallback.` 298 | ); 299 | } 300 | } 301 | 302 | return formattedMessage || message || defaultMessage || id; 303 | } 304 | 305 | export function formatHTMLMessage(config, state, messageDescriptor, rawValues = {}) { 306 | // Process all the values before they are used when formatting the ICU 307 | // Message string. Since the formatted message might be injected via 308 | // `innerHTML`, all String-based values need to be HTML-escaped. 309 | let escapedValues = Object.keys(rawValues).reduce((escaped, name) => { 310 | let value = rawValues[name]; 311 | escaped[name] = typeof value === 'string' ? escape(value) : value; 312 | return escaped; 313 | }, {}); 314 | 315 | return formatMessage(config, state, messageDescriptor, escapedValues); 316 | } 317 | -------------------------------------------------------------------------------- /src/getLocaleData.js: -------------------------------------------------------------------------------- 1 | export default function getLocaleData(Vue) { 2 | const locale = Vue.locale; 3 | const format_data = Object.getOwnPropertyNames((Vue.__allFormats || {})[locale] || {}).length > 0; 4 | const formats = format_data ? Vue.__allFormats[locale] : Vue.__format_config.defaultFormats; 5 | const messages = (Vue.__allMessages || {})[locale] || {}; 6 | return { 7 | formats, 8 | messages, 9 | defaultLocale: Vue.__format_config.defaultLocale, 10 | defaultFormats: Vue.__format_config.defaultFormats 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * This software is distributed under an MIT license, see accompanying LICENSE file for details. 4 | */ 5 | 6 | /* 7 | * Many files in this package are modified versions of those used in the react-intl package. 8 | * https://github.com/yahoo/react-intl 9 | * The license notice below is provided to comply with the terms of the BSD license of that package. 10 | */ 11 | 12 | /* 13 | * Copyright 2015, Yahoo Inc. 14 | * Copyrights licensed under the New BSD License. 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are met: 17 | * 18 | * * Redistributions of source code must retain the above copyright 19 | * notice, this list of conditions and the following disclaimer. 20 | * 21 | * * Redistributions in binary form must reproduce the above copyright 22 | * notice, this list of conditions and the following disclaimer in the 23 | * documentation and/or other materials provided with the distribution. 24 | * 25 | * * Neither the name of the Yahoo Inc. nor the 26 | * names of its contributors may be used to endorse or promote products 27 | * derived from this software without specific prior written permission. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 30 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 31 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 32 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 33 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 35 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 36 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 37 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 38 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | */ 40 | 41 | import allLocaleData from '../locale-data/index'; 42 | import {addLocaleData} from './vue-intl'; 43 | 44 | export * from './vue-intl'; 45 | 46 | addLocaleData(allLocaleData); 47 | -------------------------------------------------------------------------------- /src/localeData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a modified version of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | import IntlMessageFormat from 'intl-messageformat'; 37 | import IntlRelativeFormat from 'intl-relativeformat'; 38 | 39 | export function addLocaleData(data = []) { 40 | let locales = Array.isArray(data) ? data : [data]; 41 | 42 | locales.forEach((localeData) => { 43 | if (localeData && localeData.locale) { 44 | IntlMessageFormat.__addLocaleData(localeData); 45 | IntlRelativeFormat.__addLocaleData(localeData); 46 | } 47 | }); 48 | } 49 | 50 | export function hasLocaleData(locale) { 51 | let localeParts = (locale || '').split('-'); 52 | 53 | while (localeParts.length > 0) { 54 | if (hasIMFAndIRFLocaleData(localeParts.join('-'))) { 55 | return true; 56 | } 57 | 58 | localeParts.pop(); 59 | } 60 | 61 | return false; 62 | } 63 | 64 | function hasIMFAndIRFLocaleData(locale) { 65 | let normalizedLocale = locale && locale.toLowerCase(); 66 | 67 | return !!( 68 | IntlMessageFormat.__localeData__[normalizedLocale] && 69 | IntlRelativeFormat.__localeData__[normalizedLocale] 70 | ); 71 | } 72 | 73 | export function registerMessages(Vue, locale, messages) { 74 | Vue.__allMessages = Vue.__allMessages || {}; 75 | Vue.__allMessages[locale] = Vue.__allMessages[locale] || {}; 76 | Object.assign(Vue.__allMessages[locale], messages); 77 | } 78 | 79 | export function registerFormats(Vue, locale, formats) { 80 | Vue.__allFormats = Vue.__allFormats || {}; 81 | Vue.__allFormats[locale] = Vue.__allFormats[locale] || {}; 82 | Object.assign(Vue.__allFormats[locale], formats); 83 | } 84 | -------------------------------------------------------------------------------- /src/plural.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is an unmodified copy of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | 37 | // This is a "hack" until a proper `intl-pluralformat` package is created. 38 | 39 | import IntlMessageFormat from 'intl-messageformat'; 40 | 41 | function resolveLocale(locales) { 42 | // IntlMessageFormat#_resolveLocale() does not depend on `this`. 43 | return IntlMessageFormat.prototype._resolveLocale(locales); 44 | } 45 | 46 | function findPluralFunction(locale) { 47 | // IntlMessageFormat#_findPluralFunction() does not depend on `this`. 48 | return IntlMessageFormat.prototype._findPluralRuleFunction(locale); 49 | } 50 | 51 | export default class IntlPluralFormat { 52 | constructor(locales, options = {}) { 53 | let useOrdinal = options.style === 'ordinal'; 54 | let pluralFn = findPluralFunction(resolveLocale(locales)); 55 | 56 | this.format = (value) => pluralFn(value, useOrdinal); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/setLocale.js: -------------------------------------------------------------------------------- 1 | export default function setLocale(Vue, locale) { 2 | Vue.set(Vue, 'locale', locale); 3 | const format_data = Object.getOwnPropertyNames((Vue.__allFormats || {})[locale] || {}).length > 0; 4 | Vue.__format_config.formats = format_data ? Vue.__allFormats[locale] : Vue.__format_config.defaultFormats; 5 | Vue.__format_config.messages = (Vue.__allMessages || {})[locale] || {}; 6 | } 7 | -------------------------------------------------------------------------------- /src/state.js: -------------------------------------------------------------------------------- 1 | import IntlMessageFormat from 'intl-messageformat'; 2 | import IntlRelativeFormat from 'intl-relativeformat'; 3 | import IntlPluralFormat from './plural'; 4 | import memoizeIntlConstructor from 'intl-format-cache'; 5 | 6 | const state = { 7 | getDateTimeFormat: memoizeIntlConstructor(Intl.DateTimeFormat), 8 | getNumberFormat : memoizeIntlConstructor(Intl.NumberFormat), 9 | getMessageFormat : memoizeIntlConstructor(IntlMessageFormat), 10 | getRelativeFormat: memoizeIntlConstructor(IntlRelativeFormat), 11 | getPluralFormat : memoizeIntlConstructor(IntlPluralFormat), 12 | now: Date.now 13 | }; 14 | 15 | export default state; 16 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a modified version of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | const bool = { type : Boolean }, 37 | number = { type : Number }, 38 | string = { type : String }, 39 | object = { type : Object }, 40 | oneOf = function(array) { 41 | return {validator: function(value) { 42 | return array.indexOf(value) > -1; 43 | }}; 44 | }; 45 | 46 | export const intlConfigPropTypes = { 47 | locale : string, 48 | formats : object, 49 | messages: object, 50 | 51 | defaultLocale : string, 52 | defaultFormats: object 53 | }; 54 | 55 | export const dateTimeFormatPropTypes = { 56 | localeMatcher: oneOf(['best fit', 'lookup']), 57 | formatMatcher: oneOf(['basic', 'best fit']), 58 | 59 | timeZone: string, 60 | hour12 : bool, 61 | 62 | weekday : oneOf(['narrow', 'short', 'long']), 63 | era : oneOf(['narrow', 'short', 'long']), 64 | year : oneOf(['numeric', '2-digit']), 65 | month : oneOf(['numeric', '2-digit', 'narrow', 'short', 'long']), 66 | day : oneOf(['numeric', '2-digit']), 67 | hour : oneOf(['numeric', '2-digit']), 68 | minute : oneOf(['numeric', '2-digit']), 69 | second : oneOf(['numeric', '2-digit']), 70 | timeZoneName: oneOf(['short', 'long']) 71 | }; 72 | 73 | export const numberFormatPropTypes = { 74 | localeMatcher: oneOf(['best fit', 'lookup']), 75 | 76 | style : oneOf(['decimal', 'currency', 'percent']), 77 | currency : string, 78 | currencyDisplay: oneOf(['symbol', 'code', 'name']), 79 | useGrouping : bool, 80 | 81 | minimumIntegerDigits : number, 82 | minimumFractionDigits : number, 83 | maximumFractionDigits : number, 84 | minimumSignificantDigits: number, 85 | maximumSignificantDigits: number 86 | }; 87 | 88 | export const relativeFormatPropTypes = { 89 | style: oneOf(['best fit', 'numeric']), 90 | units: oneOf(['second', 'minute', 'hour', 'day', 'month', 'year']) 91 | }; 92 | 93 | export const pluralFormatPropTypes = { 94 | style: oneOf(['cardinal', 'ordinal']) 95 | }; 96 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML escaping is the same as React's 3 | (on purpose.) Therefore, it has the following Copyright and Licensing: 4 | 5 | Copyright 2013-2014, Facebook, Inc. 6 | All rights reserved. 7 | 8 | This source code is licensed under the BSD-style license found in the LICENSE 9 | file in the root directory of React's source tree. 10 | */ 11 | 12 | const ESCAPED_CHARS = { 13 | '&' : '&', 14 | '>' : '>', 15 | '<' : '<', 16 | '"' : '"', 17 | '\'': ''' 18 | }; 19 | 20 | const UNSAFE_CHARS_REGEX = /[&><"']/g; 21 | 22 | export function escape(str) { 23 | return ('' + str).replace(UNSAFE_CHARS_REGEX, (match) => ESCAPED_CHARS[match]); 24 | } 25 | 26 | export function filterProps(props, whitelist, defaults = {}) { 27 | return whitelist.reduce((filtered, name) => { 28 | if (props.hasOwnProperty(name)) { 29 | filtered[name] = props[name]; 30 | } else if (defaults.hasOwnProperty(name)) { 31 | filtered[name] = defaults[name]; 32 | } 33 | return filtered; 34 | }, {}); 35 | } 36 | 37 | /* 38 | First check if { path: { nested: 'content' }} exists to be returned, 39 | or return 'path.nested' key’s content (previous default behavior) 40 | */ 41 | export function getMessage(messages, messagePath) { 42 | let message; 43 | 44 | try { 45 | const deepMessage = messagePath.split('.').reduce((namespace, prop) => { 46 | return namespace && namespace[prop]; 47 | }, messages); 48 | 49 | if (typeof deepMessage !== "string") { 50 | throw new Error("Path not found"); 51 | } 52 | 53 | message = deepMessage; 54 | } catch(e) { 55 | message = messages[messagePath]; 56 | } 57 | 58 | return message; 59 | } 60 | -------------------------------------------------------------------------------- /src/vue-intl.js: -------------------------------------------------------------------------------- 1 | import {addLocaleData, registerMessages, registerFormats} from './localeData'; 2 | import setLocale from './setLocale'; 3 | import getLocaleData from './getLocaleData'; 4 | import state from './state'; 5 | import * as formatMethods from './format'; 6 | import FormattedMessage from './FormattedMessage' 7 | import defaultLocaleData from './en'; 8 | 9 | addLocaleData(defaultLocaleData); 10 | 11 | 12 | function install(Vue, options={}) { 13 | Vue.addLocaleData = addLocaleData; 14 | Vue.registerMessages = registerMessages.bind(null, Vue); 15 | Vue.registerFormats = registerFormats.bind(null, Vue); 16 | Vue.setLocale = setLocale.bind(null, Vue); 17 | Vue.__format_state = state; 18 | Vue.__format_config = { 19 | defaultLocale: options.defaultLocale || 'en', 20 | defaultFormats: options.defaultFormats || {} 21 | }; 22 | 23 | for (let key of Object.getOwnPropertyNames(formatMethods).filter((name) => { 24 | return formatMethods[name] instanceof Function; 25 | })) { 26 | Vue.prototype[`\$${key}`] = function(...args) { 27 | let config = {locale: Vue.locale}; 28 | Object.assign(config, getLocaleData(Vue)); 29 | const state = Vue.__format_state; 30 | return formatMethods[key](config, state, ...args); 31 | } 32 | } 33 | } 34 | 35 | export default { 36 | addLocaleData, 37 | FormattedMessage, 38 | install, 39 | } 40 | -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register 2 | --require intl 3 | --recursive 4 | test/unit/*.js -------------------------------------------------------------------------------- /test/unit/format.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a modified version of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | import expect, {createSpy, spyOn} from 'expect'; 37 | import IntlMessageFormat from 'intl-messageformat'; 38 | import IntlRelativeFormat from 'intl-relativeformat'; 39 | import IntlPluralFormat from '../../src/plural'; 40 | import * as f from '../../src/format'; 41 | 42 | describe('format API', () => { 43 | const {NODE_ENV} = process.env; 44 | const IRF_THRESHOLDS = Object.assign({}, IntlRelativeFormat.thresholds); 45 | 46 | let consoleError; 47 | let config; 48 | let state; 49 | 50 | beforeEach(() => { 51 | consoleError = spyOn(console, 'error'); 52 | 53 | config = { 54 | locale: 'en', 55 | 56 | messages: { 57 | no_args: 'Hello, World!', 58 | with_arg: 'Hello, {name}!', 59 | with_named_format: 'It is {now, date, year-only}', 60 | with_html: 'Hello, {name}!', 61 | 62 | missing: undefined, 63 | empty: '', 64 | invalid: 'invalid {}', 65 | missing_value: 'missing {arg_missing}', 66 | missing_named_format: 'missing {now, date, format_missing}', 67 | namespace: { 68 | deep: { 69 | no_args: 'Hello, World!' 70 | }, 71 | with_arg: 'Hello, {name}!' 72 | }, 73 | "dot.namespace": 'Hello, World!' 74 | }, 75 | 76 | formats: { 77 | date: { 78 | 'year-only': { 79 | year: 'numeric' 80 | }, 81 | missing: undefined 82 | }, 83 | 84 | time: { 85 | 'hour-only': { 86 | hour: '2-digit', 87 | hour12: false 88 | }, 89 | missing: undefined 90 | }, 91 | 92 | relative: { 93 | 'seconds': { 94 | units: 'second' 95 | }, 96 | missing: undefined 97 | }, 98 | 99 | number: { 100 | 'percent': { 101 | style: 'percent', 102 | minimumFractionDigits: 2 103 | }, 104 | missing: undefined 105 | } 106 | }, 107 | 108 | defaultLocale: 'en', 109 | defaultFormats: {} 110 | }; 111 | 112 | state = { 113 | getDateTimeFormat: createSpy().andCall((...args) => new Intl.DateTimeFormat(...args)), 114 | getNumberFormat : createSpy().andCall((...args) => new Intl.NumberFormat(...args)), 115 | getMessageFormat : createSpy().andCall((...args) => new IntlMessageFormat(...args)), 116 | getRelativeFormat: createSpy().andCall((...args) => new IntlRelativeFormat(...args)), 117 | getPluralFormat : createSpy().andCall((...args) => new IntlPluralFormat(...args)), 118 | 119 | now: () => 0 120 | }; 121 | }); 122 | 123 | afterEach(() => { 124 | process.env.NODE_ENV = NODE_ENV; 125 | consoleError.restore(); 126 | }); 127 | 128 | describe('formatDate()', () => { 129 | let df; 130 | let formatDate; 131 | 132 | beforeEach(() => { 133 | df = new Intl.DateTimeFormat(config.locale); 134 | formatDate = f.formatDate.bind(null, config, state); 135 | }); 136 | 137 | it('fallsback and warns when no value is provided', () => { 138 | expect(formatDate()).toBe('Invalid Date'); 139 | expect(consoleError.calls.length).toBe(1); 140 | expect(consoleError.calls[0].arguments[0]).toContain( 141 | '[Vue Intl] Error formatting date.\nRangeError' 142 | ); 143 | }); 144 | 145 | it('fallsback and warns when a non-finite value is provided', () => { 146 | expect(formatDate(NaN)).toBe('Invalid Date'); 147 | expect(formatDate('')).toBe('Invalid Date'); 148 | expect(consoleError.calls.length).toBe(2); 149 | }); 150 | 151 | it('formats falsy finite values', () => { 152 | expect(formatDate(false)).toBe(df.format(false)); 153 | expect(formatDate(null)).toBe(df.format(null)); 154 | expect(formatDate(0)).toBe(df.format(0)); 155 | }); 156 | 157 | it('formats date instance values', () => { 158 | expect(formatDate(new Date(0))).toBe(df.format(new Date(0))); 159 | }); 160 | 161 | it('formats date string values', () => { 162 | expect(formatDate(new Date(0).toString())).toBe(df.format(new Date(0))); 163 | }); 164 | 165 | it('formats date ms timestamp values', () => { 166 | const timestamp = Date.now(); 167 | expect(formatDate(timestamp)).toBe(df.format(timestamp)); 168 | }); 169 | 170 | describe('options', () => { 171 | it('accepts empty options', () => { 172 | expect(formatDate(0, {})).toBe(df.format(0)); 173 | }); 174 | 175 | it('accepts valid Intl.DateTimeFormat options', () => { 176 | expect(() => formatDate(0, {year: 'numeric'})).toNotThrow(); 177 | }); 178 | 179 | it('fallsback and warns on invalid Intl.DateTimeFormat options', () => { 180 | expect(formatDate(0, {year: 'invalid'})).toBe(String(new Date(0))); 181 | expect(consoleError.calls.length).toBe(1); 182 | expect(consoleError.calls[0].arguments[0]).toContain( 183 | '[Vue Intl] Error formatting date.\nRangeError' 184 | ); 185 | }); 186 | 187 | it('uses configured named formats', () => { 188 | const date = new Date(); 189 | const format = 'year-only'; 190 | 191 | const {locale, formats} = config; 192 | df = new Intl.DateTimeFormat(locale, formats.date[format]); 193 | 194 | expect(formatDate(date, {format})).toBe(df.format(date)); 195 | }); 196 | 197 | it('uses named formats as defaults', () => { 198 | const date = new Date(); 199 | const opts = {month: 'numeric'}; 200 | const format = 'year-only'; 201 | 202 | const {locale, formats} = config; 203 | df = new Intl.DateTimeFormat(locale, Object.assign({}, 204 | formats.date[format], 205 | opts 206 | )); 207 | 208 | expect(formatDate(date, Object.assign({format}, opts))).toBe(df.format(date)); 209 | }); 210 | 211 | it('handles missing named formats and warns', () => { 212 | const date = new Date(); 213 | const format = 'missing'; 214 | 215 | df = new Intl.DateTimeFormat(config.locale); 216 | 217 | expect(formatDate(date, {format})).toBe(df.format(date)); 218 | expect(consoleError.calls.length).toBe(1); 219 | expect(consoleError.calls[0].arguments[0]).toBe( 220 | `[Vue Intl] No date format named: ${format}` 221 | ); 222 | }); 223 | }); 224 | }); 225 | 226 | describe('formatTime()', () => { 227 | let df; 228 | let formatTime; 229 | 230 | beforeEach(() => { 231 | df = new Intl.DateTimeFormat(config.locale, { 232 | hour: 'numeric', 233 | minute: 'numeric' 234 | }); 235 | 236 | formatTime = f.formatTime.bind(null, config, state); 237 | }); 238 | 239 | it('fallsback and warns when no value is provided', () => { 240 | expect(formatTime()).toBe('Invalid Date'); 241 | expect(consoleError.calls.length).toBe(1); 242 | expect(consoleError.calls[0].arguments[0]).toContain( 243 | '[Vue Intl] Error formatting time.\nRangeError' 244 | ); 245 | }); 246 | 247 | it('fallsback and warns when a non-finite value is provided', () => { 248 | expect(formatTime(NaN)).toBe('Invalid Date'); 249 | expect(formatTime('')).toBe('Invalid Date'); 250 | expect(consoleError.calls.length).toBe(2); 251 | }); 252 | 253 | it('formats falsy finite values', () => { 254 | expect(formatTime(false)).toBe(df.format(false)); 255 | expect(formatTime(null)).toBe(df.format(null)); 256 | expect(formatTime(0)).toBe(df.format(0)); 257 | }); 258 | 259 | it('formats date instance values', () => { 260 | expect(formatTime(new Date(0))).toBe(df.format(new Date(0))); 261 | }); 262 | 263 | it('formats date string values', () => { 264 | expect(formatTime(new Date(0).toString())).toBe(df.format(new Date(0))); 265 | }); 266 | 267 | it('formats date ms timestamp values', () => { 268 | const timestamp = Date.now(); 269 | expect(formatTime(timestamp)).toBe(df.format(timestamp)); 270 | }); 271 | 272 | describe('options', () => { 273 | it('accepts empty options', () => { 274 | expect(formatTime(0, {})).toBe(df.format(0)); 275 | }); 276 | 277 | it('accepts valid Intl.DateTimeFormat options', () => { 278 | expect(() => formatTime(0, {hour: '2-digit'})).toNotThrow(); 279 | }); 280 | 281 | it('fallsback and warns on invalid Intl.DateTimeFormat options', () => { 282 | expect(formatTime(0, {hour: 'invalid'})).toBe(String(new Date(0))); 283 | expect(consoleError.calls.length).toBe(1); 284 | expect(consoleError.calls[0].arguments[0]).toContain( 285 | '[Vue Intl] Error formatting time.\nRangeError' 286 | ); 287 | }); 288 | 289 | it('uses configured named formats', () => { 290 | const date = new Date(); 291 | const format = 'hour-only'; 292 | 293 | const {locale, formats} = config; 294 | df = new Intl.DateTimeFormat(locale, formats.time[format]); 295 | 296 | expect(formatTime(date, {format})).toBe(df.format(date)); 297 | }); 298 | 299 | it('uses named formats as defaults', () => { 300 | const date = new Date(); 301 | const opts = {minute: '2-digit'}; 302 | const format = 'hour-only'; 303 | 304 | const {locale, formats} = config; 305 | df = new Intl.DateTimeFormat(locale, Object.assign({}, 306 | opts, 307 | formats.time[format] 308 | )); 309 | 310 | expect(formatTime(date, Object.assign({format}, opts))).toBe(df.format(date)); 311 | }); 312 | 313 | it('handles missing named formats and warns', () => { 314 | const date = new Date(); 315 | const format = 'missing'; 316 | 317 | expect(formatTime(date, {format})).toBe(df.format(date)); 318 | expect(consoleError.calls.length).toBe(1); 319 | expect(consoleError.calls[0].arguments[0]).toBe( 320 | `[Vue Intl] No time format named: ${format}` 321 | ); 322 | }); 323 | }); 324 | }); 325 | 326 | describe('formatRelative()', () => { 327 | let now; 328 | let rf; 329 | let formatRelative; 330 | 331 | beforeEach(() => { 332 | now = state.now(); 333 | rf = new IntlRelativeFormat(config.locale); 334 | formatRelative = f.formatRelative.bind(null, config, state); 335 | }); 336 | 337 | it('fallsback and warns when no value is provided', () => { 338 | expect(formatRelative()).toBe('Invalid Date'); 339 | expect(consoleError.calls.length).toBe(1); 340 | expect(consoleError.calls[0].arguments[0]).toContain( 341 | '[Vue Intl] Error formatting relative time.\nRangeError' 342 | ); 343 | }); 344 | 345 | it('fallsback and warns when a non-finite value is provided', () => { 346 | expect(formatRelative(NaN)).toBe('Invalid Date'); 347 | expect(formatRelative('')).toBe('Invalid Date'); 348 | expect(consoleError.calls.length).toBe(2); 349 | }); 350 | 351 | it('formats falsy finite values', () => { 352 | expect(formatRelative(false)).toBe(rf.format(false, {now})); 353 | expect(formatRelative(null)).toBe(rf.format(null, {now})); 354 | expect(formatRelative(0)).toBe(rf.format(0, {now})); 355 | }); 356 | 357 | it('formats date instance values', () => { 358 | expect(formatRelative(new Date(0))).toBe(rf.format(new Date(0), {now})); 359 | }); 360 | 361 | it('formats date string values', () => { 362 | expect(formatRelative(new Date(0).toString())).toBe(rf.format(new Date(0), {now})); 363 | }); 364 | 365 | it('formats date ms timestamp values', () => { 366 | const timestamp = Date.now(); 367 | expect(formatRelative(timestamp)).toBe(rf.format(timestamp, {now})); 368 | }); 369 | 370 | it('formats with the expected thresholds', () => { 371 | const timestamp = now - (1000 * 59); 372 | expect(IntlRelativeFormat.thresholds).toEqual(IRF_THRESHOLDS); 373 | expect(formatRelative(timestamp)).toNotBe(rf.format(timestamp, {now})); 374 | expect(formatRelative(timestamp)).toBe('59 seconds ago'); 375 | expect(IntlRelativeFormat.thresholds).toEqual(IRF_THRESHOLDS); 376 | expect(formatRelative(NaN)).toBe('Invalid Date'); 377 | expect(IntlRelativeFormat.thresholds).toEqual(IRF_THRESHOLDS); 378 | }); 379 | 380 | describe('options', () => { 381 | it('accepts empty options', () => { 382 | expect(formatRelative(0, {})).toBe(rf.format(0, {now})); 383 | }); 384 | 385 | it('accepts valid IntlRelativeFormat options', () => { 386 | expect(() => formatRelative(0, {units: 'second'})).toNotThrow(); 387 | }); 388 | 389 | it('fallsback and wanrs on invalid IntlRelativeFormat options', () => { 390 | expect(formatRelative(0, {units: 'invalid'})).toBe(String(new Date(0))); 391 | expect(consoleError.calls.length).toBe(1); 392 | expect(consoleError.calls[0].arguments[0]).toBe( 393 | '[Vue Intl] Error formatting relative time.\nError: "invalid" is not a valid IntlRelativeFormat `units` value, it must be one of: "second", "minute", "hour", "day", "month", "year"' 394 | ); 395 | }); 396 | 397 | it('uses configured named formats', () => { 398 | const date = -(1000 * 120); 399 | const format = 'seconds'; 400 | 401 | const {locale, formats} = config; 402 | rf = new IntlRelativeFormat(locale, formats.relative[format]); 403 | 404 | expect(formatRelative(date, {format})).toBe(rf.format(date, {now})); 405 | }); 406 | 407 | it('uses named formats as defaults', () => { 408 | const date = 0; 409 | const opts = {style: 'numeric'}; 410 | const format = 'seconds'; 411 | 412 | const {locale, formats} = config; 413 | rf = new IntlRelativeFormat(locale, Object.assign({}, 414 | opts, 415 | formats.relative[format] 416 | )); 417 | 418 | expect(formatRelative(date, Object.assign({}, opts, format))).toBe(rf.format(date, {now})); 419 | }); 420 | 421 | it('handles missing named formats and warns', () => { 422 | const date = new Date(); 423 | const format = 'missing'; 424 | 425 | rf = new IntlRelativeFormat(config.locale); 426 | 427 | expect(formatRelative(date, {format})).toBe(rf.format(date, {now})); 428 | expect(consoleError.calls.length).toBe(1); 429 | expect(consoleError.calls[0].arguments[0]).toBe( 430 | `[Vue Intl] No relative format named: ${format}` 431 | ); 432 | }); 433 | 434 | describe('now', () => { 435 | it('accepts a `now` option', () => { 436 | now = 1000; 437 | expect(formatRelative(0, {now})).toBe(rf.format(0, {now})); 438 | }); 439 | 440 | it('defaults to `state.now()` when no value is provided', () => { 441 | now = 2000; 442 | state.now = () => now; 443 | 444 | expect(formatRelative(1000)).toBe(rf.format(1000, {now})); 445 | }); 446 | 447 | it('does not throw or warn when a non-finite value is provided', () => { 448 | expect(() => formatRelative(0, {now: NaN})).toNotThrow(); 449 | expect(() => formatRelative(0, {now: ''})).toNotThrow(); 450 | expect(consoleError.calls.length).toBe(0); 451 | }); 452 | 453 | it('formats falsy finite values', () => { 454 | expect(formatRelative(0, {now: false})).toBe(rf.format(0, {now: false})); 455 | expect(formatRelative(0, {now: null})).toBe(rf.format(0, {now: null})); 456 | expect(formatRelative(0, {now: 0})).toBe(rf.format(0, {now: 0})); 457 | }); 458 | 459 | it('formats date instance values', () => { 460 | now = new Date(1000); 461 | expect(formatRelative(0, {now})).toBe(rf.format(0, {now})); 462 | }); 463 | 464 | it('formats date string values', () => { 465 | now = 1000; 466 | const dateString = new Date(now).toString(); 467 | expect(formatRelative(0, {now: dateString})).toBe(rf.format(0, {now})); 468 | }); 469 | 470 | it('formats date ms timestamp values', () => { 471 | now = 1000; 472 | expect(formatRelative(0, {now})).toBe(rf.format(0, {now})); 473 | }); 474 | }); 475 | }); 476 | }); 477 | 478 | describe('formatNumber()', () => { 479 | let nf; 480 | let formatNumber; 481 | 482 | beforeEach(() => { 483 | nf = new Intl.NumberFormat(config.locale); 484 | formatNumber = f.formatNumber.bind(null, config, state); 485 | }); 486 | 487 | it('returns "NaN" when no value is provided', () => { 488 | expect(nf.format()).toBe('NaN'); 489 | expect(formatNumber()).toBe('NaN'); 490 | }); 491 | 492 | it('returns "NaN" when a non-number value is provided', () => { 493 | expect(nf.format(NaN)).toBe('NaN'); 494 | expect(formatNumber(NaN)).toBe('NaN'); 495 | }); 496 | 497 | it('formats falsy values', () => { 498 | expect(formatNumber(false)).toBe(nf.format(false)); 499 | expect(formatNumber(null)).toBe(nf.format(null)); 500 | expect(formatNumber('')).toBe(nf.format('')); 501 | expect(formatNumber(0)).toBe(nf.format(0)); 502 | }); 503 | 504 | it('formats number values', () => { 505 | expect(formatNumber(1000)).toBe(nf.format(1000)); 506 | expect(formatNumber(1.1)).toBe(nf.format(1.1)); 507 | }); 508 | 509 | it('formats string values parsed as numbers', () => { 510 | expect(Number('1000')).toBe(1000); 511 | expect(formatNumber('1000')).toBe(nf.format('1000')); 512 | expect(Number('1.10')).toBe(1.1); 513 | expect(formatNumber('1.10')).toBe(nf.format('1.10')); 514 | }); 515 | 516 | describe('options', () => { 517 | it('accepts empty options', () => { 518 | expect(formatNumber(1000, {})).toBe(nf.format(1000)); 519 | }); 520 | 521 | it('accepts valid Intl.NumberFormat options', () => { 522 | expect(() => formatNumber(0, {style: 'percent'})).toNotThrow(); 523 | }); 524 | 525 | it('fallsback and warns on invalid Intl.NumberFormat options', () => { 526 | expect(formatNumber(0, {style: 'invalid'})).toBe(String(0)); 527 | expect(consoleError.calls.length).toBe(1); 528 | expect(consoleError.calls[0].arguments[0]).toContain( 529 | '[Vue Intl] Error formatting number.\nRangeError' 530 | ); 531 | }); 532 | 533 | it('uses configured named formats', () => { 534 | const num = 0.505; 535 | const format = 'percent'; 536 | 537 | const {locale, formats} = config; 538 | nf = new Intl.NumberFormat(locale, formats.number[format]); 539 | 540 | expect(formatNumber(num, {format})).toBe(nf.format(num)); 541 | }); 542 | 543 | it('uses named formats as defaults', () => { 544 | const num = 0.500059; 545 | const opts = {maximumFractionDigits: 3}; 546 | const format = 'percent'; 547 | 548 | const {locale, formats} = config; 549 | nf = new Intl.NumberFormat(locale, Object.assign({}, 550 | opts, 551 | formats.number[format] 552 | )); 553 | 554 | expect(formatNumber(num, Object.assign({format}, opts))).toBe(nf.format(num)); 555 | }); 556 | 557 | it('handles missing named formats and warns', () => { 558 | const num = 1000; 559 | const format = 'missing'; 560 | 561 | nf = new Intl.NumberFormat(config.locale); 562 | 563 | expect(formatNumber(num, {format})).toBe(nf.format(num)); 564 | expect(consoleError.calls.length).toBe(1); 565 | expect(consoleError.calls[0].arguments[0]).toBe( 566 | `[Vue Intl] No number format named: ${format}` 567 | ); 568 | }); 569 | }); 570 | }); 571 | 572 | describe('formatPlural()', () => { 573 | let pf; 574 | let formatPlural; 575 | 576 | beforeEach(() => { 577 | pf = new IntlPluralFormat(config.locale); 578 | formatPlural = f.formatPlural.bind(null, config, state); 579 | }); 580 | 581 | it('formats falsy values', () => { 582 | expect(formatPlural(undefined)).toBe(pf.format(undefined)); 583 | expect(formatPlural(false)).toBe(pf.format(false)); 584 | expect(formatPlural(null)).toBe(pf.format(null)); 585 | expect(formatPlural(NaN)).toBe(pf.format(NaN)); 586 | expect(formatPlural('')).toBe(pf.format('')); 587 | expect(formatPlural(0)).toBe(pf.format(0)); 588 | }); 589 | 590 | it('formats integer values', () => { 591 | expect(formatPlural(0)).toBe(pf.format(0)); 592 | expect(formatPlural(1)).toBe(pf.format(1)); 593 | expect(formatPlural(2)).toBe(pf.format(2)); 594 | }); 595 | 596 | it('formats decimal values', () => { 597 | expect(formatPlural(0.1)).toBe(pf.format(0.1)); 598 | expect(formatPlural(1.0)).toBe(pf.format(1.0)); 599 | expect(formatPlural(1.1)).toBe(pf.format(1.1)); 600 | }); 601 | 602 | it('formats string values parsed as numbers', () => { 603 | expect(Number('0')).toBe(0); 604 | expect(formatPlural('0')).toBe(pf.format('0')); 605 | expect(Number('1')).toBe(1); 606 | expect(formatPlural('1')).toBe(pf.format('1')); 607 | 608 | expect(Number('0.1')).toBe(0.1); 609 | expect(formatPlural('0.1')).toBe(pf.format('0.1')); 610 | expect(Number('1.0')).toBe(1.0); 611 | expect(formatPlural('1.0')).toBe(pf.format('1.0')); 612 | }); 613 | 614 | describe('options', () => { 615 | it('accepts empty options', () => { 616 | expect(formatPlural(0, {})).toBe(pf.format(0)); 617 | }); 618 | 619 | it('accepts valid IntlPluralFormat options', () => { 620 | expect(() => formatPlural(22, {style: 'ordinal'})).toNotThrow(); 621 | }); 622 | 623 | describe('ordinals', () => { 624 | it('formats using ordinal plural rules', () => { 625 | const opts = {style: 'ordinal'}; 626 | pf = new IntlPluralFormat(config.locale, opts); 627 | 628 | expect(formatPlural(22, opts)).toBe(pf.format(22)); 629 | }); 630 | }); 631 | }); 632 | }); 633 | 634 | describe('formatMessage()', () => { 635 | let formatMessage; 636 | 637 | beforeEach(() => { 638 | formatMessage = f.formatMessage.bind(null, config, state); 639 | }); 640 | 641 | it('throws when no Message Descriptor is provided', () => { 642 | expect(() => formatMessage()).toThrow( 643 | '[Vue Intl] An `id` must be provided to format a message.' 644 | ); 645 | }); 646 | 647 | it('throws when Message Descriptor `id` is missing or falsy', () => { 648 | expect(() => formatMessage({})).toThrow( 649 | '[Vue Intl] An `id` must be provided to format a message.' 650 | ); 651 | 652 | [undefined, null, false, 0, ''].forEach((id) => { 653 | expect(() => formatMessage({id: id})).toThrow( 654 | '[Vue Intl] An `id` must be provided to format a message.' 655 | ); 656 | }); 657 | }); 658 | 659 | it('formats basic messages', () => { 660 | const {locale, messages} = config; 661 | const mf = new IntlMessageFormat(messages.no_args, locale); 662 | 663 | expect(formatMessage({id: 'no_args'})).toBe(mf.format()); 664 | }); 665 | 666 | it('formats basic namespaced messages', () => { 667 | const {locale, messages} = config; 668 | const mf = new IntlMessageFormat(messages.namespace.deep.no_args, locale); 669 | 670 | expect(formatMessage({id: 'namespace.deep.no_args'})).toBe(mf.format()); 671 | }); 672 | 673 | it('formats dot-namespaced messages', () => { 674 | const {locale, messages} = config; 675 | const mf = new IntlMessageFormat(messages["dot.namespace"], locale); 676 | 677 | expect(formatMessage({id: 'dot.namespace'})).toBe(mf.format()); 678 | }); 679 | 680 | it('formats messages with placeholders', () => { 681 | const {locale, messages} = config; 682 | const mf = new IntlMessageFormat(messages.with_arg, locale); 683 | const values = {name: 'Eric'}; 684 | 685 | expect(formatMessage({id: 'with_arg'}, values)).toBe(mf.format(values)); 686 | }); 687 | 688 | it('formats namespaced messages with placeholders', () => { 689 | const {locale, messages} = config; 690 | const mf = new IntlMessageFormat(messages.namespace.with_arg, locale); 691 | const values = {name: 'Eric'}; 692 | 693 | expect(formatMessage({id: 'namespace.with_arg'}, values)).toBe(mf.format(values)); 694 | }); 695 | 696 | it('formats messages with named formats', () => { 697 | const {locale, messages, formats} = config; 698 | const mf = new IntlMessageFormat(messages.with_named_format, locale, formats); 699 | const values = {now: Date.now()}; 700 | 701 | expect(formatMessage({id: 'with_named_format'}, values)).toBe(mf.format(values)); 702 | }); 703 | 704 | it('avoids formatting when no values and in production', () => { 705 | const {messages} = config; 706 | 707 | process.env.NODE_ENV = 'production'; 708 | expect(formatMessage({id: 'no_args'})).toBe(messages.no_args); 709 | expect(state.getMessageFormat.calls.length).toBe(0); 710 | 711 | const values = {foo: 'foo'}; 712 | expect(formatMessage({id: 'no_args'}, values)).toBe(messages.no_args); 713 | expect(state.getMessageFormat.calls.length).toBe(1); 714 | 715 | process.env.NODE_ENV = 'development'; 716 | expect(formatMessage({id: 'no_args'})).toBe(messages.no_args); 717 | expect(state.getMessageFormat.calls.length).toBe(2); 718 | }); 719 | 720 | describe('fallbacks', () => { 721 | it('formats message with missing named formats', () => { 722 | const {locale, messages} = config; 723 | const mf = new IntlMessageFormat(messages.missing_named_format, locale); 724 | const values = {now: Date.now()}; 725 | 726 | expect(formatMessage({id: 'missing_named_format'}, values)).toBe(mf.format(values)); 727 | }); 728 | 729 | it('formats `defaultMessage` when message is missing', () => { 730 | const {locale, messages} = config; 731 | const mf = new IntlMessageFormat(messages.with_arg, locale); 732 | const id = 'missing'; 733 | const values = {name: 'Eric'}; 734 | 735 | expect(formatMessage({ 736 | id: id, 737 | defaultMessage: messages.with_arg 738 | }, values)).toBe(mf.format(values)); 739 | }); 740 | 741 | it('warns when `message` is missing and locales are different', () => { 742 | config.locale = 'fr'; 743 | 744 | let {locale, messages, defaultLocale} = config; 745 | let mf = new IntlMessageFormat(messages.with_arg, locale); 746 | let id = 'missing'; 747 | let values = {name: 'Eric'}; 748 | 749 | expect(locale).toNotEqual(defaultLocale); 750 | 751 | expect(formatMessage({ 752 | id: id, 753 | defaultMessage: messages.with_arg 754 | }, values)).toBe(mf.format(values)); 755 | 756 | expect(consoleError.calls.length).toBe(1); 757 | expect(consoleError.calls[0].arguments[0]).toContain( 758 | `[Vue Intl] Missing message: "${id}" for locale: "${locale}", using default message as fallback.` 759 | ); 760 | }); 761 | 762 | it('warns when `message` and `defaultMessage` are missing', () => { 763 | let {locale, messages} = config; 764 | let id = 'missing'; 765 | let values = {name: 'Eric'}; 766 | 767 | expect(formatMessage({ 768 | id: id, 769 | defaultMessage: messages.missing 770 | }, values)).toBe(id); 771 | 772 | expect(consoleError.calls.length).toBe(2); 773 | expect(consoleError.calls[0].arguments[0]).toContain( 774 | `[Vue Intl] Missing message: "${id}" for locale: "${locale}"` 775 | ); 776 | expect(consoleError.calls[1].arguments[0]).toContain( 777 | `[Vue Intl] Cannot format message: "${id}", using message id as fallback.` 778 | ); 779 | }); 780 | 781 | it('formats `defaultMessage` when message has a syntax error', () => { 782 | const {locale, messages} = config; 783 | const mf = new IntlMessageFormat(messages.with_arg, locale); 784 | const id = 'invalid'; 785 | const values = {name: 'Eric'}; 786 | 787 | expect(formatMessage({ 788 | id: id, 789 | defaultMessage: messages.with_arg 790 | }, values)).toBe(mf.format(values)); 791 | 792 | expect(consoleError.calls.length).toBe(1); 793 | expect(consoleError.calls[0].arguments[0]).toContain( 794 | `[Vue Intl] Error formatting message: "${id}" for locale: "${locale}", using default message as fallback.` 795 | ); 796 | }); 797 | 798 | it('formats `defaultMessage` when message has missing values', () => { 799 | const {locale, messages} = config; 800 | const mf = new IntlMessageFormat(messages.with_arg, locale); 801 | const id = 'missing_value'; 802 | const values = {name: 'Eric'}; 803 | 804 | expect(formatMessage({ 805 | id: id, 806 | defaultMessage: messages.with_arg 807 | }, values)).toBe(mf.format(values)); 808 | 809 | expect(consoleError.calls.length).toBe(1); 810 | expect(consoleError.calls[0].arguments[0]).toContain( 811 | `[Vue Intl] Error formatting message: "${id}" for locale: "${locale}", using default message as fallback.` 812 | ); 813 | }); 814 | 815 | it('returns message source when message and `defaultMessage` have formatting errors', () => { 816 | const {locale, messages} = config; 817 | const id = 'missing_value'; 818 | 819 | expect(formatMessage({ 820 | id: id, 821 | defaultMessage: messages.invalid 822 | })).toBe(messages[id]); 823 | 824 | expect(consoleError.calls.length).toBe(3); 825 | expect(consoleError.calls[0].arguments[0]).toContain( 826 | `[Vue Intl] Error formatting message: "${id}" for locale: "${locale}"` 827 | ); 828 | expect(consoleError.calls[1].arguments[0]).toContain( 829 | `[Vue Intl] Error formatting the default message for: "${id}"` 830 | ); 831 | expect(consoleError.calls[2].arguments[0]).toContain( 832 | `[Vue Intl] Cannot format message: "${id}", using message source as fallback.` 833 | ); 834 | }); 835 | 836 | it('returns message source when formatting error and missing `defaultMessage`', () => { 837 | const {locale, messages} = config; 838 | const id = 'missing_value'; 839 | 840 | expect(formatMessage({ 841 | id: id, 842 | defaultMessage: messages.missing 843 | })).toBe(messages[id]); 844 | 845 | expect(consoleError.calls.length).toBe(2); 846 | expect(consoleError.calls[0].arguments[0]).toContain( 847 | `[Vue Intl] Error formatting message: "${id}" for locale: "${locale}"` 848 | ); 849 | expect(consoleError.calls[1].arguments[0]).toContain( 850 | `[Vue Intl] Cannot format message: "${id}", using message source as fallback.` 851 | ); 852 | }); 853 | 854 | it('returns `defaultMessage` source when formatting errors and missing message', () => { 855 | config.locale = 'en-US'; 856 | 857 | const {locale, messages} = config; 858 | const id = 'missing'; 859 | 860 | expect(formatMessage({ 861 | id: id, 862 | defaultMessage: messages.invalid 863 | })).toBe(messages.invalid); 864 | 865 | expect(consoleError.calls.length).toBe(3); 866 | expect(consoleError.calls[0].arguments[0]).toContain( 867 | `[Vue Intl] Missing message: "${id}" for locale: "${locale}", using default message as fallback.` 868 | ); 869 | expect(consoleError.calls[1].arguments[0]).toContain( 870 | `[Vue Intl] Error formatting the default message for: "${id}"` 871 | ); 872 | expect(consoleError.calls[2].arguments[0]).toContain( 873 | `[Vue Intl] Cannot format message: "${id}", using message source as fallback.` 874 | ); 875 | }); 876 | 877 | it('returns message `id` when message and `defaultMessage` are missing', () => { 878 | const id = 'missing'; 879 | 880 | expect(formatMessage({id: id})).toBe(id); 881 | 882 | expect(consoleError.calls.length).toBe(2); 883 | expect(consoleError.calls[0].arguments[0]).toContain( 884 | `[Vue Intl] Missing message: "${id}" for locale: "${config.locale}"` 885 | ); 886 | expect(consoleError.calls[1].arguments[0]).toContain( 887 | `[Vue Intl] Cannot format message: "${id}", using message id as fallback.` 888 | ); 889 | }); 890 | 891 | it('returns message `id` when message and `defaultMessage` are empty', () => { 892 | const {locale, messages} = config; 893 | const id = 'empty'; 894 | 895 | expect(formatMessage({ 896 | id: id, 897 | defaultMessage: messages[id] 898 | })).toBe(id); 899 | 900 | expect(consoleError.calls.length).toBe(2); 901 | expect(consoleError.calls[0].arguments[0]).toContain( 902 | `[Vue Intl] Missing message: "${id}" for locale: "${locale}"` 903 | ); 904 | expect(consoleError.calls[1].arguments[0]).toContain( 905 | `[Vue Intl] Cannot format message: "${id}", using message id as fallback.` 906 | ); 907 | }); 908 | }); 909 | }); 910 | 911 | describe('formatHTMLMessage()', () => { 912 | let formatHTMLMessage; 913 | 914 | beforeEach(() => { 915 | formatHTMLMessage = f.formatHTMLMessage.bind(null, config, state); 916 | }); 917 | 918 | it('formats HTML messages', () => { 919 | const {locale, messages} = config; 920 | const mf = new IntlMessageFormat(messages.with_html, locale); 921 | const values = {name: 'Eric'}; 922 | 923 | expect(formatHTMLMessage({id: 'with_html'}, values)).toBe(mf.format(values)); 924 | }); 925 | 926 | it('html-escapes string values', () => { 927 | const {locale, messages} = config; 928 | const mf = new IntlMessageFormat(messages.with_html, locale); 929 | const values = {name: 'Eric'}; 930 | const escapedValues = {name: '<i>Eric</i>'}; 931 | 932 | expect(formatHTMLMessage({id: 'with_html'}, values)).toBe(mf.format(escapedValues)); 933 | }); 934 | }); 935 | }); 936 | -------------------------------------------------------------------------------- /test/unit/localeData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is a modified version of that used in the react-intl package. 3 | * https://github.com/yahoo/react-intl 4 | * The license notice below is provided to comply with the terms of the BSD license of that package. 5 | */ 6 | 7 | /* 8 | * Copyright 2015, Yahoo Inc. 9 | * Copyrights licensed under the New BSD License. 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * * Neither the name of the Yahoo Inc. nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | import * as localeData from '../../src/localeData'; 37 | import expect, {createSpy, spyOn} from 'expect'; 38 | 39 | import IntlMessageFormat from 'intl-messageformat'; 40 | import IntlRelativeFormat from 'intl-relativeformat'; 41 | 42 | describe("localeData module", () => { 43 | let VueSpy; 44 | const IMF_LOCALE_DATA = Object.assign({}, IntlMessageFormat.__localeData__); 45 | const IRF_LOCALE_DATA = Object.assign({}, IntlRelativeFormat.__localeData__); 46 | 47 | const emptyLocaleData = () => { 48 | const emptyObject = (obj) => { 49 | Object.keys(obj).forEach((prop) => delete obj[prop]); 50 | }; 51 | 52 | emptyObject(IntlMessageFormat.__localeData__); 53 | emptyObject(IntlRelativeFormat.__localeData__); 54 | }; 55 | 56 | const restoreLocaleData = () => { 57 | emptyLocaleData(); 58 | Object.assign(IntlMessageFormat.__localeData__, IMF_LOCALE_DATA); 59 | Object.assign(IntlRelativeFormat.__localeData__, IRF_LOCALE_DATA); 60 | }; 61 | beforeEach(() => { 62 | VueSpy = class { 63 | constructor() { 64 | this.test = 'test'; 65 | } 66 | }; 67 | }); 68 | afterEach(() => { 69 | VueSpy = undefined; 70 | }); 71 | describe("addLocaleData", () => { 72 | beforeEach(() => { 73 | emptyLocaleData(); 74 | }); 75 | 76 | afterEach(() => { 77 | restoreLocaleData(); 78 | }); 79 | it("is a function", () => { 80 | expect(localeData.addLocaleData).toBeA('function'); 81 | }); 82 | it('does not throw when called with no arguments', () => { 83 | expect(() => localeData.addLocaleData()).toNotThrow(); 84 | }); 85 | 86 | it('adds locale data to the registry', () => { 87 | expect(localeData.hasLocaleData('testlang')).toBe(false); 88 | 89 | localeData.addLocaleData({locale: 'testlang'}); 90 | expect(localeData.hasLocaleData('testlang')).toBe(true); 91 | }); 92 | 93 | it('accepts an array of locale data', () => { 94 | 95 | const locale = 'test'; 96 | expect(localeData.hasLocaleData(locale)).toBe(false); 97 | 98 | localeData.addLocaleData([{locale: 'test'}, {locale: 'notest'}]); 99 | expect(localeData.hasLocaleData(locale)).toBe(true); 100 | }); 101 | }); 102 | describe('hasLocaleData()', () => { 103 | beforeEach(() => { 104 | emptyLocaleData(); 105 | // "en" is guaranteed to be included by default. 106 | IntlMessageFormat.__addLocaleData(IMF_LOCALE_DATA.en); 107 | IntlRelativeFormat.__addLocaleData(IRF_LOCALE_DATA.en); 108 | }); 109 | 110 | it('does not throw when called with no arguments', () => { 111 | expect(() => localeData.hasLocaleData()).toNotThrow(); 112 | }); 113 | 114 | it('returns `false` when called with no arguments', () => { 115 | expect(localeData.hasLocaleData()).toBe(false); 116 | }); 117 | 118 | it('returns `true` for built-in "en" locale', () => { 119 | expect(localeData.hasLocaleData('en')).toBe(true); 120 | }); 121 | 122 | it('normalizes the passed-in locale', () => { 123 | expect(localeData.hasLocaleData('EN')).toBe(true); 124 | expect(localeData.hasLocaleData('eN')).toBe(true); 125 | expect(localeData.hasLocaleData('En')).toBe(true); 126 | }); 127 | 128 | it('delegates to IntlMessageFormat and IntlRelativeFormat', () => { 129 | emptyLocaleData(); 130 | expect(localeData.hasLocaleData('en')).toBe(false); 131 | 132 | IntlMessageFormat.__addLocaleData(IMF_LOCALE_DATA.en); 133 | IntlRelativeFormat.__addLocaleData(IRF_LOCALE_DATA.en); 134 | expect(localeData.hasLocaleData('en')).toBe(true); 135 | }); 136 | 137 | it('requires both IntlMessageFormat and IntlRelativeFormat to have locale data', () => { 138 | emptyLocaleData(); 139 | IntlMessageFormat.__addLocaleData(IMF_LOCALE_DATA.en); 140 | expect(localeData.hasLocaleData('en')).toBe(false); 141 | }); 142 | }); 143 | describe("registerMessages", () => { 144 | it("is a function", () => { 145 | expect(localeData.registerMessages).toBeA('function'); 146 | }); 147 | it("sets an allMessages object when undefined", () => { 148 | expect(VueSpy.__allMessages).toNotExist(); 149 | localeData.registerMessages(VueSpy, "test-language", {test: "should be here"}); 150 | expect(VueSpy.__allMessages).toExist(); 151 | }); 152 | it("sets an allMessages for locale to messages arg when undefined", () => { 153 | VueSpy.__allMessages = {}; 154 | expect(VueSpy.__allMessages['test-language']).toNotExist(); 155 | localeData.registerMessages(VueSpy, "test-language", {test: "should be here"}); 156 | expect(VueSpy.__allMessages['test-language']).toEqual({test: "should be here"}); 157 | }); 158 | it("merges an allMessages for locale with messages arg when defined", () => { 159 | VueSpy.__allMessages = {}; 160 | VueSpy.__allMessages['test-language'] = {notest: 'here', test: 'not here'}; 161 | localeData.registerMessages(VueSpy, "test-language", {test: "should be here"}); 162 | expect(VueSpy.__allMessages['test-language']).toEqual({notest: 'here', test: "should be here"}); 163 | }); 164 | }); 165 | describe("registerFormats", () => { 166 | it("is a function", () => { 167 | expect(localeData.registerFormats).toBeA('function'); 168 | }); 169 | it("sets an allFormats object when undefined", () => { 170 | expect(VueSpy.__allFormats).toNotExist(); 171 | localeData.registerFormats(VueSpy, "test-language", {test: "should be here"}); 172 | expect(VueSpy.__allFormats).toExist(); 173 | }); 174 | it("sets an allFormats for locale to formats arg when undefined", () => { 175 | VueSpy.__allFormats = {}; 176 | expect(VueSpy.__allFormats['test-language']).toNotExist(); 177 | localeData.registerFormats(VueSpy, "test-language", {test: "should be here"}); 178 | expect(VueSpy.__allFormats['test-language']).toEqual({test: "should be here"}); 179 | }); 180 | it("merges an allFormats for locale with formats arg when defined", () => { 181 | VueSpy.__allFormats = {}; 182 | VueSpy.__allFormats['test-language'] = {notest: 'here', test: 'not here'}; 183 | localeData.registerFormats(VueSpy, "test-language", {test: "should be here"}); 184 | expect(VueSpy.__allFormats['test-language']).toEqual({notest: 'here', test: "should be here"}); 185 | }); 186 | }); 187 | }); -------------------------------------------------------------------------------- /test/unit/setLocale.js: -------------------------------------------------------------------------------- 1 | import setLocale from '../../src/setLocale'; 2 | import expect, {createSpy, spyOn} from 'expect'; 3 | 4 | describe("setLocale", () => { 5 | let VueSpy; 6 | beforeEach(() => { 7 | VueSpy = class { 8 | constructor() { 9 | this.test = 'test'; 10 | } 11 | }; 12 | VueSpy.set = createSpy(); 13 | VueSpy.__format_config = {}; 14 | }); 15 | afterEach(() => { 16 | VueSpy.set.restore(); 17 | delete VueSpy.set; 18 | VueSpy = undefined; 19 | }); 20 | it("calls set on the passed in Vue constructor", () => { 21 | setLocale(VueSpy, "test-language"); 22 | expect(VueSpy.set).toHaveBeenCalledWith(VueSpy,'locale', 'test-language'); 23 | }); 24 | it("sets format config to locale format if available", () => { 25 | const lang = "test-language"; 26 | const formats = {test: 'test'}; 27 | VueSpy.__allFormats = {}; 28 | VueSpy.__allFormats[lang] = formats; 29 | setLocale(VueSpy, lang); 30 | expect(VueSpy.__format_config.formats).toEqual(formats); 31 | }); 32 | it("sets format config to default format if locale format is unavailable", () => { 33 | const lang = "test-language"; 34 | const formats = {test: 'test'}; 35 | VueSpy.__format_config.defaultFormats = formats; 36 | setLocale(VueSpy, lang); 37 | expect(VueSpy.__format_config.formats).toEqual(formats); 38 | }); 39 | it("sets messages config to locale messages if available", () => { 40 | const lang = "test-language"; 41 | const messages = {test: 'test'}; 42 | VueSpy.__allMessages = {}; 43 | VueSpy.__allMessages[lang] = messages; 44 | setLocale(VueSpy, lang); 45 | expect(VueSpy.__format_config.messages).toEqual(messages); 46 | }); 47 | it("sets messages config to empty object if locale messages are unavailable", () => { 48 | const lang = "test-language"; 49 | setLocale(VueSpy, lang); 50 | expect(VueSpy.__format_config.messages).toEqual({}); 51 | }); 52 | }); -------------------------------------------------------------------------------- /test/unit/types.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import {dateTimeFormatPropTypes} from '../../src/types'; 3 | 4 | describe("validator function", () => { 5 | it("should only accept one of ['narrow', 'short', 'long'] for a weekday format option", () => { 6 | expect(dateTimeFormatPropTypes.weekday.validator('narrow')).toBe(true); 7 | expect(dateTimeFormatPropTypes.weekday.validator('short')).toBe(true); 8 | expect(dateTimeFormatPropTypes.weekday.validator('long')).toBe(true); 9 | expect(dateTimeFormatPropTypes.weekday.validator('wide')).toBe(false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/unit/vue-intl.js: -------------------------------------------------------------------------------- 1 | import expect, {createSpy, spyOn} from 'expect'; 2 | import VueIntl from '../../src/vue-intl'; 3 | import * as formatMethods from '../../src/format'; 4 | 5 | describe("VueIntl API", () => { 6 | let VueSpy; 7 | beforeEach(() => { 8 | VueSpy = class { 9 | constructor () { 10 | this.test = 'test'; 11 | } 12 | }; 13 | VueIntl.install(VueSpy); 14 | }); 15 | afterEach(() => { 16 | VueSpy = undefined; 17 | }); 18 | describe("addLocaleData", () => { 19 | it("is present", () => { 20 | expect(VueSpy.addLocaleData).toExist(); 21 | }); 22 | }); 23 | describe("registerMessages", () => { 24 | it("is present", () => { 25 | expect(VueSpy.registerMessages).toExist(); 26 | }); 27 | it("modifies the global Vue instance", () => { 28 | VueSpy.registerMessages("test-language", {test: "should be here"}); 29 | expect(VueSpy.__allMessages["test-language"].test).toEqual("should be here", `registerMessages did not 30 | modify the bound Vue instance`); 31 | }); 32 | }); 33 | describe("registerFormats", () => { 34 | it("is present", () => { 35 | expect(VueSpy.registerFormats).toExist(); 36 | }); 37 | it("modifies the global Vue instance", () => { 38 | VueSpy.registerFormats("test-language", {test: "should be here"}); 39 | expect(VueSpy.__allFormats["test-language"].test).toEqual("should be here", `registerFormats did not 40 | modify the bound Vue instance`); 41 | }); 42 | }); 43 | describe("setLocale", () => { 44 | beforeEach(() => { 45 | const setSpy = createSpy(); 46 | VueSpy.set = setSpy; 47 | }); 48 | afterEach(() => { 49 | VueSpy.set.restore(); 50 | delete VueSpy.set; 51 | }); 52 | it("is present", () => { 53 | expect(VueSpy.setLocale).toExist(); 54 | }); 55 | it("calls set on the global Vue instance", () => { 56 | VueSpy.setLocale("test-language"); 57 | expect(VueSpy.set).toHaveBeenCalledWith(VueSpy, 'locale', 'test-language'); 58 | }); 59 | }); 60 | describe("__format_state", () => { 61 | it("is present", () => { 62 | expect(VueSpy.__format_state).toExist(); 63 | }); 64 | }); 65 | describe("__format_config", () => { 66 | it("is present", () => { 67 | expect(VueSpy.__format_config).toExist(); 68 | }); 69 | }); 70 | describe("prototype methods", () => { 71 | const methods = Object.getOwnPropertyNames(formatMethods).filter((name) => { 72 | return formatMethods[name] instanceof Function; 73 | }); 74 | methods.forEach((test) => { 75 | describe(test, () => { 76 | it(`${test} is present`, () => { 77 | expect(VueSpy.prototype[`\$${test}`]).toExist(); 78 | }); 79 | it(`${test} is invoked with local config state`, () => { 80 | let methodSpy = spyOn(formatMethods, test); 81 | const lang = 'test-language'; 82 | VueSpy.locale = lang; 83 | const config = { 84 | formats: {test: "test"}, 85 | messages: {hello: "hello"}, 86 | defaultLocale: lang, 87 | defaultFormats: {untest: "untest"} 88 | }; 89 | const state = {}; 90 | VueSpy.__allFormats = { 91 | [lang]: {test: "test"} 92 | }; 93 | VueSpy.__allMessages = { 94 | [lang]: {hello: "hello"} 95 | }; 96 | VueSpy.__format_config = config; 97 | VueSpy.__format_state = state; 98 | const vue = new VueSpy(); 99 | vue[`\$${test}`](); 100 | expect(methodSpy).toHaveBeenCalledWith(Object.assign({locale: lang}, config), state); 101 | }); 102 | }); 103 | 104 | }); 105 | }); 106 | describe("prototype.$formatTime", () => { 107 | it("is present", () => { 108 | expect(VueSpy.prototype.$formatTime).toExist(); 109 | }); 110 | }); 111 | describe("prototype.$formatRelative", () => { 112 | it("is present", () => { 113 | expect(VueSpy.prototype.$formatRelative).toExist(); 114 | }); 115 | }); 116 | describe("prototype.$formatNumber", () => { 117 | it("is present", () => { 118 | expect(VueSpy.prototype.$formatNumber).toExist(); 119 | }); 120 | }); 121 | describe("prototype.$formatPlural", () => { 122 | it("is present", () => { 123 | expect(VueSpy.prototype.$formatPlural).toExist(); 124 | }); 125 | }); 126 | describe("prototype.$formatMessage", () => { 127 | it("is present", () => { 128 | expect(VueSpy.prototype.$formatMessage).toExist(); 129 | }); 130 | }); 131 | describe("prototype.$formatHTMLMessage", () => { 132 | it("is present", () => { 133 | expect(VueSpy.prototype.$formatHTMLMessage).toExist(); 134 | }); 135 | }); 136 | }); --------------------------------------------------------------------------------