Fix bug 466451 - Improved rendering of iMIP information in message view. r=philipp
authorLaurent Jouanneau <laurent@xulfr.org>
Thu, 23 Aug 2012 00:53:19 +0200
changeset 10944 c1fc1371bc3379a0a66cbb94a5cf8d51da81344c
parent 10943 d9388937bc4b7f236935d32ad222cfada4a62ed2
child 10945 29fbdc5bfaadbbbfb34540e12fe466d41adeee53
push id8207
push usermozilla@kewis.ch
push dateWed, 22 Aug 2012 22:56:21 +0000
treeherdercomm-central@c1fc1371bc33 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp
bugs466451
Fix bug 466451 - Improved rendering of iMIP information in message view. r=philipp
calendar/base/content/calendar-task-view.js
calendar/base/content/dialogs/calendar-dialog-utils.js
calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
calendar/base/content/dialogs/calendar-event-dialog.js
calendar/base/content/dialogs/calendar-summary-dialog.js
calendar/base/modules/Makefile.in
calendar/base/modules/calRecurrenceUtils.jsm
calendar/lightning/components/lightningTextCalendarConverter.js
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
--- a/calendar/base/content/calendar-task-view.js
+++ b/calendar/base/content/calendar-task-view.js
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 var taskDetailsView = {
 
     /**
      * Task Details Events
      *
      * XXXberend Please document this function, possibly also consolidate since
      * its the only function in taskDetailsView.
@@ -108,19 +109,20 @@ var taskDetailsView = {
                 parentItem = parentItem.parentItem;
             }
             var recurrenceInfo = parentItem.recurrenceInfo;
             var recurStart = parentItem.recurrenceStartDate;
             if (displayElement("calendar-task-details-repeat-row", recurrenceInfo && recurStart)) {
                 var kDefaultTimezone = calendarDefaultTimezone();
                 var startDate = recurStart.getInTimezone(kDefaultTimezone);
                 var endDate = item.dueDate ? item.dueDate.getInTimezone(kDefaultTimezone) : null;
-                var detailsString = recurrenceRule2String(recurrenceInfo,startDate,endDate,startDate.isDate);
+                var detailsString = recurrenceRule2String(recurrenceInfo, startDate, endDate, startDate.isDate);
                 if (detailsString) {
-                    document.getElementById("calendar-task-details-repeat").value = detailsString.split("\n").join(" ");
+                    let rpv = document.getElementById("calendar-task-details-repeat");
+                    rpv.value = detailsString.split("\n").join(" ");
                 }
             }
             var textbox = document.getElementById("calendar-task-details-description");
             var description = item.hasProperty("DESCRIPTION") ? item.getProperty("DESCRIPTION") : null;
             textbox.value = description;
             textbox.inputField.readOnly = true;
             let attachmentRows = document.getElementById("calendar-task-details-attachment-rows");
             removeChildren(attachmentRows);
--- a/calendar/base/content/dialogs/calendar-dialog-utils.js
+++ b/calendar/base/content/dialogs/calendar-dialog-utils.js
@@ -1,368 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
-
-/**
- * This function takes the recurrence info passed as argument and creates a
- * literal string representing the repeat pattern in natural language.
- *
- * @param recurrenceInfo    An item's recurrence info to parse.
- * @param startDate         The start date to base rules on.
- * @param endDate           The end date to base rules on.
- * @param allDay            If true, the pattern should assume an allday item.
- * @return                  A human readable string describing the recurrence.
- */
-function recurrenceRule2String(recurrenceInfo, startDate, endDate, allDay) {
-    function getRString(name, args) calGetString("calendar-event-dialog", name, args);
-
-    // Retrieve a valid recurrence rule from the currently
-    // set recurrence info. Bail out if there's more
-    // than a single rule or something other than a rule.
-    recurrenceInfo = recurrenceInfo.clone();
-    let rrules = splitRecurrenceRules(recurrenceInfo);
-    if (rrules[0].length == 1) {
-        let rule = rrules[0][0];
-        // currently we don't allow for any BYxxx-rules.
-        if (calInstanceOf(rule, Components.interfaces.calIRecurrenceRule) &&
-            !checkRecurrenceRule(rule, ['BYSECOND',
-                                        'BYMINUTE',
-                                        //'BYDAY',
-                                        'BYHOUR',
-                                        //'BYMONTHDAY',
-                                        'BYYEARDAY',
-                                        'BYWEEKNO',
-                                        //'BYMONTH',
-                                        'BYSETPOS'])) {
-            function day_of_week(day) {
-                return Math.abs(day) % 8;
-            }
-            function day_position(day) {
-                let dow = day_of_week(day);
-                return (Math.abs(day) - dow) / 8 * (day < 0 ? -1 : 1);
-            }
-            function nounClass(aDayString, aRuleString) {
-                // Select noun class (grammatical gender) for rule string
-                let nounClass = getRString(aDayString + "Nounclass");
-                return aRuleString + nounClass.substr(0, 1).toUpperCase() +
-                       nounClass.substr(1);
-            }
-            function pluralWeekday(aDayString) {
-                let plural = getRString("pluralForWeekdays") == "true";
-                return (plural ? aDayString + "Plural" : aDayString);
-            }
-
-            let ruleString;
-            if (rule.type == 'DAILY') {
-                if (checkRecurrenceRule(rule, ['BYDAY'])) {
-                    let days = rule.getComponent("BYDAY", {});
-                    let weekdays = [2, 3, 4, 5, 6];
-                    if (weekdays.length == days.length) {
-                        let i;
-                        for (i = 0; i < weekdays.length; i++) {
-                            if (weekdays[i] != days[i]) {
-                                break;
-                            }
-                        }
-                        if (i == weekdays.length) {
-                            ruleString = getRString("repeatDetailsRuleDaily4");
-                        }
-                    } else {
-                        return getRString("ruleTooComplex");
-                    }
-                } else {
-                    let dailyString = getRString("dailyEveryNth");
-                    ruleString = PluralForm.get(rule.interval, dailyString)
-                                           .replace("#1", rule.interval);
-                }
-            } else if (rule.type == 'WEEKLY') {
-                // weekly recurrence, currently we
-                // support a single 'BYDAY'-rule only.
-                if (checkRecurrenceRule(rule, ['BYDAY'])) {
-                    // create a string like 'Monday, Tuesday and Wednesday'
-                    let days = rule.getComponent("BYDAY", {});
-                    let weekdays = "";
-                    // select noun class (grammatical gender) according to the 
-                    // first day of the list
-                    let weeklyString = nounClass("repeatDetailsDay" + days[0], "weeklyNthOn");
-                    for (let i = 0; i < days.length; i++) {
-                        if (rule.interval == 1) {
-                            weekdays += getRString(pluralWeekday("repeatDetailsDay" + days[i]));
-                        } else {
-                            weekdays += getRString("repeatDetailsDay" + days[i]);
-                        }
-                        if (days.length > 1 && i == (days.length - 2)) {
-                            weekdays += ' ' + getRString("repeatDetailsAnd") + ' ';
-                        } else if (i < days.length - 1) {
-                            weekdays += ', ';
-                        }
-                    }
-
-                    weeklyString = getRString(weeklyString, [weekdays]);
-                    ruleString= PluralForm.get(rule.interval, weeklyString)
-                                          .replace("#2", rule.interval);
-
-                } else {
-                    let weeklyString = getRString("weeklyEveryNth");
-                    ruleString = PluralForm.get(rule.interval, weeklyString)
-                                           .replace("#1", rule.interval);
-                }
-            } else if (rule.type == 'MONTHLY') {
-                if (checkRecurrenceRule(rule, ['BYDAY'])) {
-                    let weekdaysString_every = "";
-                    let weekdaysString_position = "";
-                    let byday = rule.getComponent("BYDAY", {});
-                    let firstDay = byday[0];
-                    // build two strings for weekdays with and without
-                    // "position" prefix, then join these strings
-                    for (let i = 0 ; i < byday.length; i++) {
-                        if (day_position(byday[i]) == 0) {
-                            if (!weekdaysString_every) {
-                                firstDay = byday[i];
-                            }
-                            weekdaysString_every += getRString(pluralWeekday("repeatDetailsDay" + byday[i])) + ", ";
-                        } else {
-                            if (day_position(byday[i]) < -1 || day_position(byday[i]) > 5) {
-                                // we support only weekdays with -1 as negative
-                                // position ('THE LAST ...')
-                                return getRString("ruleTooComplex");
-                            }
-                            if (byday.some(function(element) {
-                                               return (day_position(element) == 0 &&
-                                                       day_of_week(byday[i]) == day_of_week(element));
-                                           })) {
-                                // prevent to build strings such as for example:
-                                // "every Monday and the second Monday..."
-                                continue;
-                            }
-                            let ordinalString = "repeatOrdinal" + day_position(byday[i]);
-                            let dayString = "repeatDetailsDay" + day_of_week(byday[i]);
-                            ordinalString = nounClass(dayString, ordinalString);
-                            ordinalString = getRString(ordinalString);
-                            dayString = getRString(dayString);
-                            let stringOrdinalWeekday = getRString("ordinalWeekdayOrder",
-                                                                  [ordinalString, dayString]);
-                            weekdaysString_position += stringOrdinalWeekday + ", ";
-                        }
-                    }
-                    let weekdaysString = weekdaysString_every + weekdaysString_position;
-                    weekdaysString = weekdaysString.slice(0,-2).
-                                     replace(/,(?= [^,]*$)/, ' ' + getRString("repeatDetailsAnd"));
-
-                    let monthlyString = weekdaysString_every ? "monthlyEveryOfEvery" : "monthlyRuleNthOfEvery";
-                    monthlyString = nounClass("repeatDetailsDay" + day_of_week(firstDay), monthlyString);
-                    monthlyString = getRString(monthlyString, [weekdaysString]);
-                    ruleString = PluralForm.get(rule.interval, monthlyString).
-                                            replace("#2", rule.interval);
-                } else if (checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
-                    let component = rule.getComponent("BYMONTHDAY", {});
-
-                    // First, find out if the 'BYMONTHDAY' component contains
-                    // any elements with a negative value. If so we currently
-                    // don't support anything but the 'last day of the month' rule.
-                    if (component.some(function(element, index, array) {
-                                           return element < 0;
-                                       })) {
-                        if (component.length == 1 && component[0] == -1) {
-                            let monthlyString = getRString("monthlyLastDayOfNth");
-                            ruleString = PluralForm.get(rule.interval, monthlyString)
-                                                   .replace("#1", rule.interval);
-                        } else {
-                            // we don't support any other combination for now...
-                            return getRString("ruleTooComplex");
-                        }
-                    } else {
-                        if (component.length == 31 &&
-                            component.every(function (element, index, array) {
-                                                for (let i = 0; i < array.length; i++) {
-                                                    if ((index + 1) == array[i]) {
-                                                        return true;
-                                                    }
-                                                }
-                                                return false;
-                                            })) {
-                            // i.e. every day every N months
-                            ruleString = getRString("monthlyEveryDayOfNth");
-                            ruleString = PluralForm.get(rule.interval, ruleString)
-                                                   .replace("#2", rule.interval);
-                        } else {
-                            // i.e. one or more monthdays every N months
-                            let day_string = "";
-                            for (let i = 0; i < component.length; i++) {
-                                day_string += component[i];
-                                if (component.length > 1 &&
-                                    i == (component.length - 2)) {
-                                    day_string += ' ' + getRString("repeatDetailsAnd") + ' ';
-                                } else if (i < component.length-1) {
-                                    day_string += ', ';
-                                }
-                            }
-                            let monthlyString = getRString("monthlyDayOfNth", [day_string]);
-                            ruleString = PluralForm.get(rule.interval, monthlyString)
-                                                   .replace("#2", rule.interval);
-                        }
-                    }
-                } else {
-                    let monthlyString = getRString("monthlyDayOfNth", [startDate.day]);
-                    ruleString = PluralForm.get(rule.interval, monthlyString)
-                                           .replace("#2", rule.interval);
-                }
-            } else if (rule.type == 'YEARLY') {
-                let bymonth = rule.getComponent("BYMONTH", {});
-                if (checkRecurrenceRule(rule, ['BYMONTH']) &&
-                    checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
-                    let bymonthday = rule.getComponent("BYMONTHDAY", {});
-
-                    if (bymonth.length == 1 && bymonthday.length == 1) {
-                        let monthNameString = getRString("repeatDetailsMonth" + bymonth[0]);
-
-                        let yearlyString = getRString("yearlyNthOn",
-                                                      [monthNameString, bymonthday[0]]);
-                        ruleString = PluralForm.get(rule.interval, yearlyString)
-                                               .replace("#3", rule.interval);
-                    }
-                } else if (checkRecurrenceRule(rule, ['BYMONTH']) &&
-                           checkRecurrenceRule(rule, ['BYDAY'])) {
-                    let byday = rule.getComponent("BYDAY", {});
-
-                    if (bymonth.length == 1 && byday.length == 1) {
-                        let dayString = "repeatDetailsDay" + day_of_week(byday[0]);
-                        let month = getRString("repeatDetailsMonth" + bymonth[0]);
-                        if (day_position(byday[0]) == 0) {
-                            let yearlyString = "yearlyOnEveryNthOfNth";
-                            yearlyString = nounClass(dayString, yearlyString);
-                            let day = getRString(pluralWeekday(dayString));
-                            yearlyString = getRString(yearlyString, [day, month]);
-                            ruleString = PluralForm.get(rule.interval, yearlyString)
-                                                   .replace("#3", rule.interval);
-                        } else {
-                            let yearlyString = "yearlyNthOnNthOf";
-                            let ordinalString = "repeatOrdinal" + day_position(byday[0])
-                            yearlyString = nounClass(dayString, yearlyString);
-                            ordinalString = nounClass(dayString, ordinalString);
-                            let ordinal = getRString(ordinalString);
-                            let day = getRString(dayString);
-                            yearlyString = getRString(yearlyString, [ordinal, day, month]);
-                            ruleString = PluralForm.get(rule.interval, yearlyString)
-                                                   .replace("#4", rule.interval);
-                        }
-                    } else {
-                        return getRString("ruleTooComplex");
-                    }
-                } else {
-                    let monthNameString = getRString("repeatDetailsMonth" + (startDate.month + 1));
-
-                    let yearlyString = getRString("yearlyNthOn",
-                                                  [monthNameString, startDate.day]);
-                    ruleString = PluralForm.get(rule.interval, yearlyString)
-                                           .replace("#3", rule.interval);
-                }
-            }
-
-            let kDefaultTimezone = cal.calendarDefaultTimezone();
-            let dateFormatter = cal.getDateFormatter();
-
-            let detailsString;
-            if (!endDate || allDay) {
-                if (rule.isFinite) {
-                    if (rule.isByCount) {
-                        let countString = getRString("repeatCountAllDay",
-                            [ruleString,
-                             dateFormatter.formatDateShort(startDate)]);
-                        detailsString = PluralForm.get(rule.count, countString)
-                                                  .replace("#3", rule.count);
-                    } else {
-                        let untilDate = rule.untilDate.getInTimezone(kDefaultTimezone);
-                        detailsString = getRString("repeatDetailsUntilAllDay",
-                            [ruleString,
-                             dateFormatter.formatDateShort(startDate),
-                             dateFormatter.formatDateShort(untilDate)]);
-                    }
-                } else {
-                    detailsString = getRString("repeatDetailsInfiniteAllDay",
-                                               [ruleString,
-                                                dateFormatter.formatDateShort(startDate)]);
-                }
-            } else {
-                if (rule.isFinite) {
-                    if (rule.isByCount) {
-                        let countString = getRString("repeatCount",
-                            [ruleString,
-                             dateFormatter.formatDateShort(startDate),
-                             dateFormatter.formatTime(startDate),
-                             dateFormatter.formatTime(endDate) ]);
-                        detailsString = PluralForm.get(rule.count, countString)
-                                                  .replace("#5", rule.count);
-                    } else {
-                        let untilDate = rule.untilDate.getInTimezone(kDefaultTimezone);
-                        detailsString = getRString("repeatDetailsUntil",
-                            [ruleString,
-                             dateFormatter.formatDateShort(startDate),
-                             dateFormatter.formatDateShort(untilDate),
-                             dateFormatter.formatTime(startDate),
-                             dateFormatter.formatTime(endDate)]);
-                    }
-                } else {
-                    detailsString = getRString("repeatDetailsInfinite",
-                        [ruleString,
-                         dateFormatter.formatDateShort(startDate),
-                         dateFormatter.formatTime(startDate),
-                         dateFormatter.formatTime(endDate) ]);
-                }
-            }
-            return detailsString;
-        }
-    }
-    return getRString("ruleTooComplex");
-}
-
-/**
- * Split rules into negative and positive rules.
- *
- * @param recurrenceInfo    An item's recurrence info to parse.
- * @return                  An array with two elements: an array of positive
- *                            rules and an array of negative rules.
- */
-function splitRecurrenceRules(recurrenceInfo) {
-    var ritems = recurrenceInfo.getRecurrenceItems({});
-    var rules = [];
-    var exceptions = [];
-    for each (var r in ritems) {
-        if (r.isNegative) {
-            exceptions.push(r);
-        } else {
-            rules.push(r);
-        }
-    }
-    return [rules, exceptions];
-}
-
-/**
- * Check if a recurrence rule's component is valid.
- *
- * @see                     calIRecurrenceRule
- * @param aRule             The recurrence rule to check.
- * @param aArray            An array of component names to check.
- * @return                  Returns true if the rule is valid.
- */
-function checkRecurrenceRule(aRule, aArray) {
-    for each (var comp in aArray) {
-        var ruleComp = aRule.getComponent(comp, {});
-        if (ruleComp && ruleComp.length > 0) {
-            return true;
-        }
-    }
-    return false;
-}
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 /**
  * Dispose of controlling operations of this event dialog. Uses
  * window.arguments[0].job.dispose()
  */
 function dispose() {
     var args = window.arguments[0];
     if (args.job && args.job.dispose) {
--- a/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
+
 var gIsReadOnly = false;
 var gStartTime = null;
 var gEndTime = null;
 var gUntilDate = null;
 
 /**
  * Sets up the recurrence dialog from the window arguments. Takes care of filling
  * the dialog controls with the recurrence information for this window.
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 // the following variables are constructed if the jsContext this file
 // belongs to gets constructed. all those variables are meant to be accessed
 // from within this file only.
 var gStartTime = null;
 var gEndTime = null;
 var gItemDuration = null;
 var gStartTimezone = null;
@@ -2996,37 +2997,39 @@ function updateRepeatDetails() {
         let event = cal.isEvent(item);
 
         let startDate = getElementValue(event ? "event-starttime" : "todo-entrydate");
         let endDate = getElementValue(event ? "event-endtime" : "todo-duedate");
         startDate = jsDateToDateTime(startDate, kDefaultTimezone);
         endDate = jsDateToDateTime(endDate, kDefaultTimezone);
 
         let allDay = getElementValue("event-all-day", "checked");
-        let detailsString = recurrenceRule2String(
-            recurrenceInfo, startDate, endDate, allDay);
+        let detailsString = recurrenceRule2String(recurrenceInfo, startDate,
+                                                  endDate, allDay);
+
+        if (!detailsString) {
+            detailsString = cal.calGetString("calendar-event-dialog", "ruleTooComplex");
+        }
 
         // Now display the string...
-        if (detailsString) {
-            let lines = detailsString.split("\n");
-            repeatDetails.removeAttribute("collapsed");
-            while (repeatDetails.childNodes.length > lines.length) {
-                repeatDetails.removeChild(repeatDetails.lastChild);
+        let lines = detailsString.split("\n");
+        repeatDetails.removeAttribute("collapsed");
+        while (repeatDetails.childNodes.length > lines.length) {
+            repeatDetails.removeChild(repeatDetails.lastChild);
+        }
+        let numChilds = repeatDetails.childNodes.length;
+        for (let i = 0; i < lines.length; i++) {
+            if (i >= numChilds) {
+                var newNode = repeatDetails.childNodes[0]
+                                           .cloneNode(true);
+                repeatDetails.appendChild(newNode);
             }
-            let numChilds = repeatDetails.childNodes.length;
-            for (let i = 0; i < lines.length; i++) {
-                if (i >= numChilds) {
-                    var newNode = repeatDetails.childNodes[0]
-                                               .cloneNode(true);
-                    repeatDetails.appendChild(newNode);
-                }
-                repeatDetails.childNodes[i].value = lines[i];
-                repeatDetails.childNodes[i].setAttribute("tooltiptext",
-                                                         detailsString);
-            }
+            repeatDetails.childNodes[i].value = lines[i];
+            repeatDetails.childNodes[i].setAttribute("tooltiptext",
+                                                     detailsString);
         }
     } else {
         let repeatDetails = document.getElementById("repeat-details");
         repeatDetails.setAttribute("collapsed", "true");
     }
 }
 
 /**
--- a/calendar/base/content/dialogs/calendar-summary-dialog.js
+++ b/calendar/base/content/dialogs/calendar-summary-dialog.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 /**
  * Sets up the summary dialog, setting all needed fields on the dialog from the
  * item received in the window arguments.
  */
 function onLoad() {
     var args = window.arguments[0];
     var item = args.calendarEvent;
@@ -257,37 +258,38 @@ function updateRepeatDetails() {
     repeatDetails.setAttribute("collapsed", "true");
 
     // Try to create a descriptive string from the rule(s).
     var kDefaultTimezone = calendarDefaultTimezone();
     var startDate =  item.startDate || item.entryDate;
     var endDate = item.endDate || item.dueDate;
     startDate = startDate ? startDate.getInTimezone(kDefaultTimezone) : null;
     endDate = endDate ? endDate.getInTimezone(kDefaultTimezone) : null;
-    var detailsString = recurrenceRule2String(
-        recurrenceInfo, startDate, endDate, startDate.isDate);
+    var detailsString = recurrenceRule2String(recurrenceInfo, startDate,
+                                              endDate, startDate.isDate);
+
+    if (!detailsString) {
+        detailsString = cal.calGetString("calendar-event-dialog", "ruleTooComplexSummary");
+    }
 
     // Now display the string...
-    if (detailsString) {
-        var lines = detailsString.split("\n");
-        repeatDetails.removeAttribute("collapsed");
-        while (repeatDetails.childNodes.length > lines.length) {
-            repeatDetails.removeChild(repeatDetails.lastChild);
+    var lines = detailsString.split("\n");
+    repeatDetails.removeAttribute("collapsed");
+    while (repeatDetails.childNodes.length > lines.length) {
+        repeatDetails.removeChild(repeatDetails.lastChild);
+    }
+    var numChilds = repeatDetails.childNodes.length;
+    for (var i = 0; i < lines.length; i++) {
+        if (i >= numChilds) {
+            var newNode = repeatDetails.childNodes[0]
+                                       .cloneNode(true);
+            repeatDetails.appendChild(newNode);
         }
-        var numChilds = repeatDetails.childNodes.length;
-        for (var i = 0; i < lines.length; i++) {
-            if (i >= numChilds) {
-                var newNode = repeatDetails.childNodes[0]
-                                           .cloneNode(true);
-                repeatDetails.appendChild(newNode);
-            }
-            repeatDetails.childNodes[i].value = lines[i];
-            repeatDetails.childNodes[i].setAttribute("tooltiptext",
-                                                     detailsString);
-        }
+        repeatDetails.childNodes[i].value = lines[i];
+        repeatDetails.childNodes[i].setAttribute("tooltiptext", detailsString);
     }
 }
 
 /**
  * Updates the attendee listbox, displaying all attendees invited to the
  * window's item.
  */
 function updateAttendees() {
--- a/calendar/base/modules/Makefile.in
+++ b/calendar/base/modules/Makefile.in
@@ -15,13 +15,14 @@ EXTRA_JS_MODULES = \
     calAlarmUtils.jsm \
     calAuthUtils.jsm \
     calHashedArray.jsm \
     calItemUtils.jsm \
     calIteratorUtils.jsm \
     calItipUtils.jsm \
     calPrintUtils.jsm \
     calProviderUtils.jsm \
+    calRecurrenceUtils.jsm \
     calUtils.jsm \
     calXMLUtils.jsm \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calRecurrenceUtils.jsm
@@ -0,0 +1,359 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+EXPORTED_SYMBOLS = ["recurrenceRule2String", "splitRecurrenceRules", "checkRecurrenceRule"];
+
+/**
+ * This function takes the recurrence info passed as argument and creates a
+ * literal string representing the repeat pattern in natural language.
+ *
+ * @param recurrenceInfo    An item's recurrence info to parse.
+ * @param startDate         The start date to base rules on.
+ * @param endDate           The end date to base rules on.
+ * @param allDay            If true, the pattern should assume an allday item.
+ * @return                  A human readable string describing the recurrence.
+ */
+function recurrenceRule2String(recurrenceInfo, startDate, endDate, allDay) {
+    function getRString(name, args) cal.calGetString("calendar-event-dialog", name, args);
+
+    // Retrieve a valid recurrence rule from the currently
+    // set recurrence info. Bail out if there's more
+    // than a single rule or something other than a rule.
+    recurrenceInfo = recurrenceInfo.clone();
+    let rrules = splitRecurrenceRules(recurrenceInfo);
+    if (rrules[0].length == 1) {
+        let rule = rrules[0][0];
+        // currently we don't allow for any BYxxx-rules.
+        if (cal.calInstanceOf(rule, Components.interfaces.calIRecurrenceRule) &&
+            !checkRecurrenceRule(rule, ['BYSECOND',
+                                        'BYMINUTE',
+                                        //'BYDAY',
+                                        'BYHOUR',
+                                        //'BYMONTHDAY',
+                                        'BYYEARDAY',
+                                        'BYWEEKNO',
+                                        //'BYMONTH',
+                                        'BYSETPOS'])) {
+            function day_of_week(day) {
+                return Math.abs(day) % 8;
+            }
+            function day_position(day) {
+                let dow = day_of_week(day);
+                return (Math.abs(day) - dow) / 8 * (day < 0 ? -1 : 1);
+            }
+            function nounClass(aDayString, aRuleString) {
+                // Select noun class (grammatical gender) for rule string
+                let nounClass = getRString(aDayString + "Nounclass");
+                return aRuleString + nounClass.substr(0, 1).toUpperCase() +
+                       nounClass.substr(1);
+            }
+            function pluralWeekday(aDayString) {
+                let plural = getRString("pluralForWeekdays") == "true";
+                return (plural ? aDayString + "Plural" : aDayString);
+            }
+
+            let ruleString;
+            if (rule.type == 'DAILY') {
+                if (checkRecurrenceRule(rule, ['BYDAY'])) {
+                    let days = rule.getComponent("BYDAY", {});
+                    let weekdays = [2, 3, 4, 5, 6];
+                    if (weekdays.length == days.length) {
+                        let i;
+                        for (i = 0; i < weekdays.length; i++) {
+                            if (weekdays[i] != days[i]) {
+                                break;
+                            }
+                        }
+                        if (i == weekdays.length) {
+                            ruleString = getRString("repeatDetailsRuleDaily4");
+                        }
+                    } else {
+                        return null;
+                    }
+                } else {
+                    let dailyString = getRString("dailyEveryNth");
+                    ruleString = PluralForm.get(rule.interval, dailyString)
+                                           .replace("#1", rule.interval);
+                }
+            } else if (rule.type == 'WEEKLY') {
+                // weekly recurrence, currently we
+                // support a single 'BYDAY'-rule only.
+                if (checkRecurrenceRule(rule, ['BYDAY'])) {
+                    // create a string like 'Monday, Tuesday and Wednesday'
+                    let days = rule.getComponent("BYDAY", {});
+                    let weekdays = "";
+                    // select noun class (grammatical gender) according to the
+                    // first day of the list
+                    let weeklyString = nounClass("repeatDetailsDay" + days[0], "weeklyNthOn");
+                    for (let i = 0; i < days.length; i++) {
+                        if (rule.interval == 1) {
+                            weekdays += getRString(pluralWeekday("repeatDetailsDay" + days[i]));
+                        } else {
+                            weekdays += getRString("repeatDetailsDay" + days[i]);
+                        }
+                        if (days.length > 1 && i == (days.length - 2)) {
+                            weekdays += ' ' + getRString("repeatDetailsAnd") + ' ';
+                        } else if (i < days.length - 1) {
+                            weekdays += ', ';
+                        }
+                    }
+
+                    weeklyString = getRString(weeklyString, [weekdays]);
+                    ruleString= PluralForm.get(rule.interval, weeklyString)
+                                          .replace("#2", rule.interval);
+
+                } else {
+                    let weeklyString = getRString("weeklyEveryNth");
+                    ruleString = PluralForm.get(rule.interval, weeklyString)
+                                           .replace("#1", rule.interval);
+                }
+            } else if (rule.type == 'MONTHLY') {
+                if (checkRecurrenceRule(rule, ['BYDAY'])) {
+                    let weekdaysString_every = "";
+                    let weekdaysString_position = "";
+                    let byday = rule.getComponent("BYDAY", {});
+                    let firstDay = byday[0];
+                    // build two strings for weekdays with and without
+                    // "position" prefix, then join these strings
+                    for (let i = 0 ; i < byday.length; i++) {
+                        if (day_position(byday[i]) == 0) {
+                            if (!weekdaysString_every) {
+                                firstDay = byday[i];
+                            }
+                            weekdaysString_every += getRString(pluralWeekday("repeatDetailsDay" + byday[i])) + ", ";
+                        } else {
+                            if (day_position(byday[i]) < -1 || day_position(byday[i]) > 5) {
+                                // we support only weekdays with -1 as negative
+                                // position ('THE LAST ...')
+                                return null;
+                            }
+                            if (byday.some(function(element) {
+                                               return (day_position(element) == 0 &&
+                                                       day_of_week(byday[i]) == day_of_week(element));
+                                           })) {
+                                // prevent to build strings such as for example:
+                                // "every Monday and the second Monday..."
+                                continue;
+                            }
+                            let ordinalString = "repeatOrdinal" + day_position(byday[i]);
+                            let dayString = "repeatDetailsDay" + day_of_week(byday[i]);
+                            ordinalString = nounClass(dayString, ordinalString);
+                            ordinalString = getRString(ordinalString);
+                            dayString = getRString(dayString);
+                            let stringOrdinalWeekday = getRString("ordinalWeekdayOrder",
+                                                                  [ordinalString, dayString]);
+                            weekdaysString_position += stringOrdinalWeekday + ", ";
+                        }
+                    }
+                    let weekdaysString = weekdaysString_every + weekdaysString_position;
+                    weekdaysString = weekdaysString.slice(0,-2).
+                                     replace(/,(?= [^,]*$)/, ' ' + getRString("repeatDetailsAnd"));
+
+                    let monthlyString = weekdaysString_every ? "monthlyEveryOfEvery" : "monthlyRuleNthOfEvery";
+                    monthlyString = nounClass("repeatDetailsDay" + day_of_week(firstDay), monthlyString);
+                    monthlyString = getRString(monthlyString, [weekdaysString]);
+                    ruleString = PluralForm.get(rule.interval, monthlyString).
+                                            replace("#2", rule.interval);
+                } else if (checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
+                    let component = rule.getComponent("BYMONTHDAY", {});
+
+                    // First, find out if the 'BYMONTHDAY' component contains
+                    // any elements with a negative value. If so we currently
+                    // don't support anything but the 'last day of the month' rule.
+                    if (component.some(function(element, index, array) {
+                                           return element < 0;
+                                       })) {
+                        if (component.length == 1 && component[0] == -1) {
+                            let monthlyString = getRString("monthlyLastDayOfNth");
+                            ruleString = PluralForm.get(rule.interval, monthlyString)
+                                                   .replace("#1", rule.interval);
+                        } else {
+                            // we don't support any other combination for now...
+                            return null;
+                        }
+                    } else {
+                        if (component.length == 31 &&
+                            component.every(function (element, index, array) {
+                                                for (let i = 0; i < array.length; i++) {
+                                                    if ((index + 1) == array[i]) {
+                                                        return true;
+                                                    }
+                                                }
+                                                return false;
+                                            })) {
+                            // i.e. every day every N months
+                            ruleString = getRString("monthlyEveryDayOfNth");
+                            ruleString = PluralForm.get(rule.interval, ruleString)
+                                                   .replace("#2", rule.interval);
+                        } else {
+                            // i.e. one or more monthdays every N months
+                            let day_string = "";
+                            for (let i = 0; i < component.length; i++) {
+                                day_string += component[i];
+                                if (component.length > 1 &&
+                                    i == (component.length - 2)) {
+                                    day_string += ' ' + getRString("repeatDetailsAnd") + ' ';
+                                } else if (i < component.length-1) {
+                                    day_string += ', ';
+                                }
+                            }
+                            let monthlyString = getRString("monthlyDayOfNth", [day_string]);
+                            ruleString = PluralForm.get(rule.interval, monthlyString)
+                                                   .replace("#2", rule.interval);
+                        }
+                    }
+                } else {
+                    let monthlyString = getRString("monthlyDayOfNth", [startDate.day]);
+                    ruleString = PluralForm.get(rule.interval, monthlyString)
+                                           .replace("#2", rule.interval);
+                }
+            } else if (rule.type == 'YEARLY') {
+                let bymonth = rule.getComponent("BYMONTH", {});
+                if (checkRecurrenceRule(rule, ['BYMONTH']) &&
+                    checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
+                    let bymonthday = rule.getComponent("BYMONTHDAY", {});
+
+                    if (bymonth.length == 1 && bymonthday.length == 1) {
+                        let monthNameString = getRString("repeatDetailsMonth" + bymonth[0]);
+
+                        let yearlyString = getRString("yearlyNthOn",
+                                                      [monthNameString, bymonthday[0]]);
+                        ruleString = PluralForm.get(rule.interval, yearlyString)
+                                               .replace("#3", rule.interval);
+                    }
+                } else if (checkRecurrenceRule(rule, ['BYMONTH']) &&
+                           checkRecurrenceRule(rule, ['BYDAY'])) {
+                    let byday = rule.getComponent("BYDAY", {});
+
+                    if (bymonth.length == 1 && byday.length == 1) {
+                        let dayString = "repeatDetailsDay" + day_of_week(byday[0]);
+                        let month = getRString("repeatDetailsMonth" + bymonth[0]);
+                        if (day_position(byday[0]) == 0) {
+                            let yearlyString = "yearlyOnEveryNthOfNth";
+                            yearlyString = nounClass(dayString, yearlyString);
+                            let day = getRString(pluralWeekday(dayString));
+                            yearlyString = getRString(yearlyString, [day, month]);
+                            ruleString = PluralForm.get(rule.interval, yearlyString)
+                                                   .replace("#3", rule.interval);
+                        } else {
+                            let yearlyString = "yearlyNthOnNthOf";
+                            let ordinalString = "repeatOrdinal" + day_position(byday[0])
+                            yearlyString = nounClass(dayString, yearlyString);
+                            ordinalString = nounClass(dayString, ordinalString);
+                            let ordinal = getRString(ordinalString);
+                            let day = getRString(dayString);
+                            yearlyString = getRString(yearlyString, [ordinal, day, month]);
+                            ruleString = PluralForm.get(rule.interval, yearlyString)
+                                                   .replace("#4", rule.interval);
+                        }
+                    } else {
+                        return null;
+                    }
+                } else {
+                    let monthNameString = getRString("repeatDetailsMonth" + (startDate.month + 1));
+
+                    let yearlyString = getRString("yearlyNthOn",
+                                                  [monthNameString, startDate.day]);
+                    ruleString = PluralForm.get(rule.interval, yearlyString)
+                                           .replace("#3", rule.interval);
+                }
+            }
+
+            let kDefaultTimezone = cal.calendarDefaultTimezone();
+            let dateFormatter = cal.getDateFormatter();
+
+            let detailsString;
+            if (!endDate || allDay) {
+                if (rule.isFinite) {
+                    if (rule.isByCount) {
+                        let countString = getRString("repeatCountAllDay",
+                            [ruleString,
+                             dateFormatter.formatDateShort(startDate)]);
+                        detailsString = PluralForm.get(rule.count, countString)
+                                                  .replace("#3", rule.count);
+                    } else {
+                        let untilDate = rule.untilDate.getInTimezone(kDefaultTimezone);
+                        detailsString = getRString("repeatDetailsUntilAllDay",
+                            [ruleString,
+                             dateFormatter.formatDateShort(startDate),
+                             dateFormatter.formatDateShort(untilDate)]);
+                    }
+                } else {
+                    detailsString = getRString("repeatDetailsInfiniteAllDay",
+                                               [ruleString,
+                                                dateFormatter.formatDateShort(startDate)]);
+                }
+            } else {
+                if (rule.isFinite) {
+                    if (rule.isByCount) {
+                        let countString = getRString("repeatCount",
+                            [ruleString,
+                             dateFormatter.formatDateShort(startDate),
+                             dateFormatter.formatTime(startDate),
+                             dateFormatter.formatTime(endDate) ]);
+                        detailsString = PluralForm.get(rule.count, countString)
+                                                  .replace("#5", rule.count);
+                    } else {
+                        let untilDate = rule.untilDate.getInTimezone(kDefaultTimezone);
+                        detailsString = getRString("repeatDetailsUntil",
+                            [ruleString,
+                             dateFormatter.formatDateShort(startDate),
+                             dateFormatter.formatDateShort(untilDate),
+                             dateFormatter.formatTime(startDate),
+                             dateFormatter.formatTime(endDate)]);
+                    }
+                } else {
+                    detailsString = getRString("repeatDetailsInfinite",
+                        [ruleString,
+                         dateFormatter.formatDateShort(startDate),
+                         dateFormatter.formatTime(startDate),
+                         dateFormatter.formatTime(endDate) ]);
+                }
+            }
+            return detailsString;
+        }
+    }
+    return null;
+}
+
+/**
+ * Split rules into negative and positive rules.
+ *
+ * @param recurrenceInfo    An item's recurrence info to parse.
+ * @return                  An array with two elements: an array of positive
+ *                            rules and an array of negative rules.
+ */
+function splitRecurrenceRules(recurrenceInfo) {
+    var ritems = recurrenceInfo.getRecurrenceItems({});
+    var rules = [];
+    var exceptions = [];
+    for each (var r in ritems) {
+        if (r.isNegative) {
+            exceptions.push(r);
+        } else {
+            rules.push(r);
+        }
+    }
+    return [rules, exceptions];
+}
+
+/**
+ * Check if a recurrence rule's component is valid.
+ *
+ * @see                     calIRecurrenceRule
+ * @param aRule             The recurrence rule to check.
+ * @param aArray            An array of component names to check.
+ * @return                  Returns true if the rule is valid.
+ */
+function checkRecurrenceRule(aRule, aArray) {
+    for each (var comp in aArray) {
+        var ruleComp = aRule.getComponent(comp, {});
+        if (ruleComp && ruleComp.length > 0) {
+            return true;
+        }
+    }
+    return false;
+}
--- a/calendar/lightning/components/lightningTextCalendarConverter.js
+++ b/calendar/lightning/components/lightningTextCalendarConverter.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calXMLUtils.jsm");
+Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 function ltnMimeConverter() {
 }
 
 ltnMimeConverter.prototype = {
     classID: Components.ID("{c70acb08-464e-4e55-899d-b2c84c5409fa}"),
 
     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleMimeConverter]),
@@ -58,22 +59,75 @@ ltnMimeConverter.prototype = {
 
             node.appendChild(a);
 
             localText = localText.substr(endPos);
         }
     },
 
     /**
+     * Returns a header title for an ITIP item depending on the response method
+     * @param       aItipItem  the event
+     * @return string the header title
+     */
+    getItipHeader: function getItipHeader(aItipItem) {
+        let header;
+
+        if (aItipItem) {
+            let item = aItipItem.getItemList({})[0];
+            let summary = item.getProperty("SUMMARY") || "";
+            let organizer = item.organizer;
+            let organizerString = organizer.toString();
+            if (organizer.commonName) {
+                organizerString = organizer.commonName;
+            }
+
+            switch (aItipItem.responseMethod) {
+                case "REQUEST":
+                    header = cal.calGetString("lightning",
+                                              "itipRequestBody",
+                                              [organizerString, summary],
+                                              "lightning");
+                    break;
+                case "CANCEL":
+                    header = cal.calGetString("lightning",
+                                              "itipCancelBody",
+                                              [organizerString, summary],
+                                              "lightning");
+                    break;
+                case "REPLY": {
+                    // Generate proper body from my participation status
+                    let sender = cal.getInvitedAttendee(item);
+                    let statusString = (sender.participationStatus == "DECLINED" ?
+                        "itipReplyBodyDecline": "itipReplyBodyAccept");
+
+                    header = cal.calGetString("lightning",
+                                              statusString,
+                                              [sender],
+                                              "lightning");
+                    break;
+                }
+            }
+        }
+
+        if (!header) {
+            header = cal.calGetString("lightning", "imipHtml.header", null, "lightning");
+        }
+
+        return header;
+    },
+
+    /**
      * Returns the html representation of the event as a DOM document.
      *
-     * @param event     The calIItemBase to parse into html.
-     * @return          The DOM document with values filled in.
+     * @param event         The calIItemBase to parse into html.
+     * @param aNewItipItem  The parsed itip item.
+     * @return              The DOM document with values filled in.
      */
-    createHtml: function createHtml(event) {
+    createHtml: function createHtml(event, aNewItipItem) {
         // Creates HTML using the Node strings in the properties file
         let doc = cal.xml.parseFile("chrome://lightning/content/lightning-invitation.xhtml");
         let self = this;
         function field(field, contentText, linkify) {
             let descr = doc.getElementById("imipHtml-" + field + "-descr");
             if (descr) {
                 let labelText = cal.calGetString("lightning", "imipHtml." + field, null, "lightning");
                 descr.textContent = labelText;
@@ -86,20 +140,40 @@ ltnMimeConverter.prototype = {
                     self.linkifyText(contentText, content);
                 } else {
                     content.textContent = contentText;
                 }
             }
         }
 
         // Simple fields
-        field("header", null);
+        let headerDescr = doc.getElementById("imipHtml-header-descr");
+        if (headerDescr) {
+            headerDescr.textContent = this.getItipHeader(aNewItipItem);
+        }
+
         field("summary", event.title);
         field("location", event.getProperty("LOCATION"));
-        field("when", cal.getDateFormatter().formatItemInterval(event));
+
+        let dateString = cal.getDateFormatter().formatItemInterval(event);
+
+        if (event.recurrenceInfo) {
+            let kDefaultTimezone = cal.calendarDefaultTimezone();
+            let startDate =  event.startDate;
+            let endDate = event.endDate;
+            startDate = startDate ? startDate.getInTimezone(kDefaultTimezone) : null;
+            endDate = endDate ? endDate.getInTimezone(kDefaultTimezone) : null;
+            let repeatString = recurrenceRule2String(event.recurrenceInfo, startDate,
+                                                     endDate, startDate.isDate);
+            if (repeatString) {
+                dateString = repeatString;
+            }
+        }
+
+        field("when", dateString);
         field("comment", event.getProperty("COMMENT"), true);
 
         // DESCRIPTION field
         let eventDescription = (event.getProperty("DESCRIPTION") || "")
                                     /* Remove the useless "Outlookism" squiggle. */
                                     .replace("*~*~*~*~*~*~*~*~*~*", "");
         field("description", eventDescription, true);
 
@@ -154,51 +228,49 @@ ltnMimeConverter.prototype = {
                         item = exc;
                     }
                 }
                 event = item;
                 break;
             }
         }
         if (!event) {
-            return;
+            return '';
         }
 
-        // Create the HTML string for display
-        let serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
-                                   .createInstance(Components.interfaces.nsIDOMSerializer);
-        let html = serializer.serializeToString(this.createHtml(event));
+        let itipItem = null;
 
         try {
             // this.uri is the message URL that we are processing.
             // We use it to get the nsMsgHeaderSink to store the calItipItem.
             if (this.uri) {
                 let msgWindow = null;
                 try {
                     let msgUrl = this.uri.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
                     // msgWindow is optional in some scenarios
                     // (e.g. gloda in action, throws NS_ERROR_INVALID_POINTER then)
                     msgWindow = msgUrl.msgWindow;
                 } catch (exc) {
                 }
                 if (msgWindow) {
-                    let itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
+                    itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
                                              .createInstance(Components.interfaces.calIItipItem);
                     itipItem.init(data);
 
                     let sinkProps = msgWindow.msgHeaderSink.properties;
                     sinkProps.setPropertyAsInterface("itipItem", itipItem);
 
                     // Notify the observer that the itipItem is available
                     let observer = Components.classes["@mozilla.org/observer-service;1"]
                                              .getService(Components.interfaces.nsIObserverService);
                     observer.notifyObservers(null, "onItipItemCreation", 0);
                 }
             }
         } catch (e) {
             cal.ERROR("[ltnMimeConverter] convertToHTML: " + e);
         }
 
-        return html;
+        // Create the HTML string for display
+        return cal.xml.serializeDOM(this.createHtml(event, itipItem));
     }
 };
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([ltnMimeConverter]);
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
@@ -281,20 +281,25 @@ repeatDetailsInfinite=Occurs %1$S\neffec
 # %1%$ - A rule string (see above). This is the first line of the link
 # %2%$ - event start date (e.g. mm/gg/yyyy)
 # e.g. with monthlyDayOfNth and all day event:
 # "Occurs day 3 of every 5 month
 #  effective 1/1/2009"
 repeatDetailsInfiniteAllDay=Occurs %1$S\neffective %2$S.
 
 # LOCALIZATION NOTE (ruleTooComplex):
-# This string is shown in the reminder details area if our code can't handle the
+# This string is shown in the repeat details area if our code can't handle the
 # complexity of the recurrence rule yet.
 ruleTooComplex=Click here for details
 
+# LOCALIZATION NOTE (ruleTooComplexSummary):
+# This string is shown in the event summary dialog if our code can't handle the
+# complexity of the recurrence rule yet.
+ruleTooComplexSummary=Repeat details unknown
+
 # differences between the dialog for an Event or a Task
 newEvent=New Event
 newTask=New Task
 itemMenuLabelEvent=Event
 itemMenuAccesskeyEvent2=T
 itemMenuLabelTask=Task
 itemMenuAccesskeyTask2=T