]> git.ipfire.org Git - thirdparty/moment.git/commitdiff
Instance Language Configuration
authorRocky Meza <rocky@fusionbox.com>
Fri, 8 Jun 2012 04:45:42 +0000 (22:45 -0600)
committerRocky Meza <rocky@fusionbox.com>
Fri, 8 Jun 2012 04:45:42 +0000 (22:45 -0600)
I have provided a `lang` method on both moment.fn and moment.duration.fn
to allow for instances of moments and durations to have their own
language configuration.  Additionally, the `lang` method is a getter
which returns either the moment's language definition object or the
global language definition object if the moment did not have one set.

Also, I modified the code surrounding meridiem to always expect a
function.  This simplifies the formatter code, and also ensures
inheritability of language configuration values.

lang/zh-cn.js
moment.js
test/moment/lang.js

index 7ff88a19fd811579e62f2be2ee05e659415e4d94..10fad206d9224bbffca4a6e538e9d29d08200780 100644 (file)
@@ -15,7 +15,7 @@
                 LLL : "YYYY年MMMD日LT",
                 LLLL : "YYYY年MMMD日ddddLT"
             },
-            meridiem : function (hour, minute, isLower) {
+            meridiem : function (hour, minute, isUpper) {
                 if (hour < 9) {
                     return "早上";
                 } else if (hour < 11 && minute < 30) {
index fa6a7a4da2099084aae433b1f4cb925930569707..bfe89bc221b4daa8582af32c701fa7c71929f707 100644 (file)
--- a/moment.js
+++ b/moment.js
         // check for nodeJS
         hasModule = (typeof module !== 'undefined'),
 
-        // parameters to check for on the lang config
-        langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
+        // Parameters to check for on the lang config.  This list of properties
+        // will be inherited from English if not provided in a language
+        // definition.  monthsParse is also a lang config property, but it
+        // cannot be inherited and as such cannot be enumerated here.
+        langConfigProperties = 'months|monthsShort|weekdays|weekdaysShort|weekdaysMin|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
 
         // ASP.NET json date format regex
         aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
@@ -86,9 +89,9 @@
             // b = placeholder
             // t = the current moment being formatted
             // v = getValueAtKey function
-            // o = moment.ordinal function
+            // o = language.ordinal function
             // p = leftZeroFill function
-            // m = moment.meridiem value or function
+            // m = language.meridiem value or function
             M    : '(a=t.month()+1)',
             MMM  : 'v("monthsShort",t.month())',
             MMMM : 'v("months",t.month())',
             w    : '(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))',
             YY   : 'p(t.year()%100,2)',
             YYYY : 't.year()',
-            a    : 'm?m(t.hours(),t.minutes(),!1):t.hours()>11?"pm":"am"',
-            A    : 'm?m(t.hours(),t.minutes(),!0):t.hours()>11?"PM":"AM"',
+            a    : 'm(t.hours(),t.minutes(),!1)',
+            A    : 'm(t.hours(),t.minutes(),!0)',
             H    : 't.hours()',
             h    : 't.hours()%12||12',
             m    : 't.minutes()',
         this._isUTC = !!isUTC;
         this._a = date._a || null;
         date._a = null;
+        this._lang = false;
     }
 
     // Duration Constructor
         years += absRound(months / 12);
 
         data.years = years;
+
+        this._lang = false;
     }
 
 
 
     // helper for recursing long date formatting tokens
     function replaceLongDateFormatTokens(input) {
-        return moment.longDateFormat[input] || input;
+        return getLangDefinition().longDateFormat[input] || input;
     }
 
     function makeFormatFunction(format) {
             Fn = Function; // get around jshint
         // t = the current moment being formatted
         // v = getValueAtKey function
-        // o = moment.ordinal function
+        // o = language.ordinal function
         // p = leftZeroFill function
-        // m = moment.meridiem value or function
+        // m = language.meridiem value or function
         return new Fn('t', 'v', 'o', 'p', 'm', output);
     }
 
         return formatFunctions[format];
     }
 
+    // Determines which language definition to use based on a moment instance.
+    // If a moment has the _lang property set on it, it will return that
+    // language; otherwise, it will return the global currentLanguage.
+    function getLangDefinition(m) {
+        return languages[(m && m._lang) || currentLanguage];
+    }
+
     // format date using native date object
     function formatMoment(m, format) {
+        var lang = getLangDefinition(m);
+
         function getValueFromArray(key, index) {
-            return moment[key].call ? moment[key](m, format) : moment[key][index];
+            return lang[key].call ? lang[key](m, format) : lang[key][index];
         }
 
         while (localFormattingTokens.test(format)) {
             formatFunctions[format] = makeFormatFunction(format);
         }
 
-        return formatFunctions[format](m, getValueFromArray, moment.ordinal, leftZeroFill, moment.meridiem);
+        return formatFunctions[format](m, getValueFromArray, lang.ordinal, leftZeroFill, lang.meridiem);
     }
 
 
         case 'MMM' : // fall through to MMMM
         case 'MMMM' :
             for (a = 0; a < 12; a++) {
-                if (moment.monthsParse[a].test(input)) {
+                if (getLangDefinition().monthsParse[a].test(input)) {
                     datePartArray[1] = a;
                     break;
                 }
 
 
     // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
-    function substituteTimeAgo(string, number, withoutSuffix, isFuture) {
-        var rt = moment.relativeTime[string];
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
+        var rt = lang.relativeTime[string];
         return (typeof rt === 'function') ?
             rt(number || 1, !!withoutSuffix, string, isFuture) :
             rt.replace(/%d/i, number || 1);
     }
 
-    function relativeTime(milliseconds, withoutSuffix) {
+    function relativeTime(milliseconds, withoutSuffix, lang) {
         var seconds = round(Math.abs(milliseconds) / 1000),
             minutes = round(seconds / 60),
             hours = round(minutes / 60),
                 years === 1 && ['y'] || ['yy', years];
         args[2] = withoutSuffix;
         args[3] = milliseconds > 0;
+        args[4] = lang;
         return substituteTimeAgo.apply({}, args);
     }
 
         }
         var date,
             matched,
-            isUTC;
+            isUTC,
+            ret;
         // parse Moment object
         if (moment.isMoment(input)) {
             date = new Date(+input._d);
                 typeof input === 'string' ? makeDateFromString(input) :
                 new Date(input);
         }
-        return new Moment(date, isUTC);
+
+        ret = new Moment(date, isUTC);
+
+        if (moment.isMoment(input)) {
+            ret.lang(input._lang);
+        }
+
+        return ret;
     };
 
     // creating with utc
     moment.duration = function (input, key) {
         var isDuration = moment.isDuration(input),
             isNumber = (typeof input === 'number'),
-            duration = (isDuration ? input._data : (isNumber ? {} : input));
+            duration = (isDuration ? input._data : (isNumber ? {} : input)),
+            ret;
 
         if (isNumber) {
             if (key) {
             }
         }
 
-        return new Duration(duration);
+        ret = new Duration(duration);
+
+        if (isDuration) {
+            ret._lang = input._lang;
+        }
+
+        return ret;
     };
 
     // humanizeDuration
             return currentLanguage;
         }
         if (values) {
+            for (i = 0; i < langConfigProperties.length; i++) {
+                // If a language definition does not provide a value, inherit
+                // from English
+                values[langConfigProperties[i]] = values[langConfigProperties[i]] ||
+                  languages.en[langConfigProperties[i]];
+            }
+
             for (i = 0; i < 12; i++) {
                 parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i');
             }
             values.monthsParse = values.monthsParse || parse;
+
             languages[key] = values;
         }
         if (languages[key]) {
+            // deprecated, to get the language definition variables, use the
+            // moment.fn.lang method or the getLangDefinition function.
             for (i = 0; i < langConfigProperties.length; i++) {
-                moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] || 
-                    languages.en[langConfigProperties[i]];
+                moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]];
             }
             currentLanguage = key;
         } else {
         }
     };
 
-    // set default language
+    // Set default language, other languages will inherit from English.
     moment.lang('en', {
         months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
         monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
             LLL : "MMMM D YYYY LT",
             LLLL : "dddd, MMMM D YYYY LT"
         },
-        meridiem : false,
+        meridiem : function (hours, minutes, isUpper) {
+            if (hours > 11) {
+                return isUpper ? 'PM' : 'pm';
+            } else {
+                return isUpper ? 'AM' : 'am';
+            }
+        },
         calendar : {
             sameDay : '[Today at] LT',
             nextDay : '[Tomorrow at] LT',
         },
 
         from : function (time, withoutSuffix) {
-            return moment.duration(this.diff(time)).humanize(!withoutSuffix);
+            return moment.duration(this.diff(time)).lang(this._lang).humanize(!withoutSuffix);
         },
 
         fromNow : function (withoutSuffix) {
 
         calendar : function () {
             var diff = this.diff(moment().sod(), 'days', true),
-                calendar = moment.calendar,
+                calendar = this.lang().calendar,
                 allElse = calendar.sameElse,
                 format = diff < -6 ? allElse :
                 diff < -1 ? calendar.lastWeek :
 
         daysInMonth : function () {
             return moment.utc([this.year(), this.month() + 1, 0]).date();
+        },
+
+        // If passed a language key, it will set the language for this
+        // instance.  Otherwise, it will return the language configuration
+        // variables for this instance.
+        lang : function (lang) {
+            if (lang === undefined) {
+                return getLangDefinition(this);
+            } else {
+                this._lang = lang;
+                return this;
+            }
         }
     };
 
 
         humanize : function (withSuffix) {
             var difference = +this,
-                rel = moment.relativeTime,
-                output = relativeTime(difference, !withSuffix);
+                rel = this.lang().relativeTime,
+                output = relativeTime(difference, !withSuffix, this.lang());
 
             if (withSuffix) {
                 output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output);
         }
     };
 
+    moment.duration.fn.lang = moment.fn.lang;
+
     function makeDurationGetter(name) {
         moment.duration.fn[name] = function () {
             return this._data[name];
index 7ed208f0ec0c0e6454bd87df9ec1bc9acbbdfe00..86df74f00cf53984212669f3817c2ea7e735841e 100644 (file)
@@ -1,7 +1,7 @@
 var moment = require("../../moment");
 
 exports.lang = {
-    "getter" : function(test) {
+    "library getter" : function(test) {
         test.expect(4);
 
         moment.lang('en');
@@ -16,6 +16,111 @@ exports.lang = {
         moment.lang('en');
         test.equal(moment.lang(), 'en', 'Lang should reset');
 
+        test.done();
+    },
+
+    "library ensure inheritance" : function(test) {
+        test.expect(2);
+
+        moment.lang('made-up', {
+            // I put them out of order
+            months : "February_March_April_May_June_July_August_September_October_November_December_January".split("_")
+            // the rest of the properties should be inherited.
+        });
+
+        test.equal(moment([2012, 5, 6]).format('MMMM'), 'July', 'Override some of the configs');
+        test.equal(moment([2012, 5, 6]).format('MMM'), 'Jun', 'But not all of them');
+
+        test.done();
+    },
+
+    "instance lang method" : function(test) {
+        test.expect(3);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+        test.equal(moment([2012, 5, 6]).format('MMMM'), 'June', 'Normally default to global');
+        test.equal(moment([2012, 5, 6]).lang('es').format('MMMM'), 'Junio', 'Use the instance specific language');
+        test.equal(moment([2012, 5, 6]).format('MMMM'), 'June', 'Using an instance specific language does not affect other moments');
+
+        test.done();
+    },
+
+    "instance lang persists with manipulation" : function(test) {
+        test.expect(3);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+        test.equal(moment([2012, 5, 6]).lang('es').add({days: 1}).format('MMMM'), 'Junio', 'With addition');
+        test.equal(moment([2012, 5, 6]).lang('es').day(0).format('MMMM'), 'Junio', 'With day getter');
+        test.equal(moment([2012, 5, 6]).lang('es').eod().format('MMMM'), 'Junio', 'With eod');
+
+        test.done();
+    },
+
+    "instance lang persists with cloning" : function(test) {
+        test.expect(2);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+        var a = moment([2012, 5, 6]).lang('es'),
+            b = a.clone(),
+            c = moment(a);
+
+        test.equal(b.format('MMMM'), 'Junio', 'using moment.fn.clone()');
+        test.equal(b.format('MMMM'), 'Junio', 'using moment()');
+
+        test.done();
+    },
+
+    "duration lang method" : function(test) {
+        test.expect(3);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+        test.equal(moment.duration({seconds:  44}).humanize(), 'a few seconds', 'Normally default to global');
+        test.equal(moment.duration({seconds:  44}).lang('es').humanize(), 'unos segundos', 'Use the instance specific language');
+        test.equal(moment.duration({seconds:  44}).humanize(), 'a few seconds', 'Using an instance specific language does not affect other durations');
+
+        test.done();
+    },
+
+    "duration lang persists with cloning" : function(test) {
+        test.expect(1);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+        var a = moment.duration({seconds:  44}).lang('es'),
+            b = moment.duration(a);
+
+        test.equal(b.humanize(), 'unos segundos', 'using moment.duration()');
+        test.done();
+    },
+
+    "instance lang used with from" : function(test) {
+        test.expect(2);
+
+        // load Spanish
+        moment.lang('es');
+        // set global language
+        moment.lang('en');
+
+        var a = moment([2012, 5, 6]).lang('es'),
+            b = moment([2012, 5, 7]);
+
+        test.equal(a.from(b), 'hace un día', 'preserve language of first moment');
+        test.equal(b.from(a), 'in a day', 'do not preserve language of second moment');
+
         test.done();
     }
 };