├── .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 | });
--------------------------------------------------------------------------------