[FIX] calendar: translate recurrence rule

Steps to reproduce:

1. install the calendar module
2. in General settings, add the French -or any- language
3. create a new event
4. under **Options** tab choose the recurrence type to be weekly
5. select the days to repeat the event on
6. save the event
7. send an email using the **EMAIL** button, you can notice that works like (Weeks) and the days’ names are not translated but still in English

Investigation

- the translation is applied on the model level, not on the code level. So we have to explicitly/programmatically translate the options.

Discussion

- In the proposed fix, we compute the recurrence rule `name` by translating it to the current language -the event creator's language- but as this field is **stored**, it resulted in another issue that the invitation emails sent to the participants had a mix of languages: the email template being translated in each participant's language while the recurrence rule itself is translated in the owner's language being a **stored** field
- That's why the fix got extended to embed translating of the recurrence rule directly into the template.
- However there's a **_limitation_** we couldn't deal with that's there are two types of emails we are concerned with:
    - invitation emails that follows `calendar attendee` model which works correctly as discussed
    - update emails that follows `calendar event` model coming from the composer which deosn't allow to properly use partners/participants languages

Resolution

- The update emails are sent in the owner's language, with the intention to enhance the behavior in master (Task-3677327)

opw-3483319

closes odoo/odoo#151669

X-original-commit: 1d6d12ab67ffc96719c3c329cdba1cc3e3471f36
Signed-off-by: Arnaud Joset (arj) <arj@odoo.com>
Signed-off-by: Ali Hassan Youssef (alhy) <alhy@odoo.com>
This commit is contained in:
AH-Yussef
2024-02-08 18:24:09 +00:00
parent 407dafa0e8
commit 07b2c95369
5 changed files with 483 additions and 173 deletions
+4 -4
View File
@@ -74,7 +74,7 @@
</li>
</t>
<t t-if="recurrent">
<li>When: <t t-out="object.recurrence_id.name or ''">Every 1 Weeks, for 3 events</t></li>
<li>When: <t t-out="object.recurrence_id.get_recurrence_name()">Every 1 Weeks, for 3 events</t></li>
</t>
<t t-if="not object.event_id.allday and object.event_id.duration">
<li>Duration: <t t-out="('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''">0H30</t></li>
@@ -205,7 +205,7 @@
</li>
</t>
<t t-if="recurrent">
<li>When: <t t-out="object.recurrence_id.name or ''">Every 1 Weeks, for 3 events</t></li>
<li>When: <t t-out="object.recurrence_id.get_recurrence_name()">Every 1 Weeks, for 3 events</t></li>
</t>
<t t-if="not object.event_id.allday and object.event_id.duration">
<li>Duration: <t t-out="('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''">0H30</t></li>
@@ -312,7 +312,7 @@
</li>
</t>
<t t-if="recurrent">
<li>When: <t t-out="object.recurrence_id.name or ''">Every 1 Weeks, for 3 events</t></li>
<li>When: <t t-out="object.recurrence_id.get_recurrence_name()">Every 1 Weeks, for 3 events</t></li>
</t>
<t t-if="not object.event_id.allday and object.event_id.duration">
<li>Duration: <t t-out="('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''">0H30</t></li>
@@ -423,7 +423,7 @@
</li>
</t>
<t t-if="recurrent">
<li>When: <t t-out="object.recurrence_id.name or ''">Every 1 Weeks, for 3 events</t></li>
<li>When: <t t-out="object.recurrence_id.get_recurrence_name()">Every 1 Weeks, for 3 events</t></li>
</t>
<t t-if="not object.allday and object.duration">
<li>Duration:
+242 -140
View File
@@ -128,7 +128,7 @@ msgid ""
" </li>\n"
" </t>\n"
" <t t-if=\"recurrent\">\n"
" <li>When: <t t-out=\"object.recurrence_id.name or ''\">Every 1 Weeks, for 3 events</t></li>\n"
" <li>When: <t t-out=\"object.recurrence_id.get_recurrence_name()\">Every 1 Weeks, for 3 events</t></li>\n"
" </t>\n"
" <t t-if=\"not object.event_id.allday and object.event_id.duration\">\n"
" <li>Duration: <t t-out=\"('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''\">0H30</t></li>\n"
@@ -170,110 +170,6 @@ msgid ""
" "
msgstr ""
#. module: calendar
#: model:mail.template,body_html:calendar.calendar_template_meeting_invitation
msgid ""
"<div>\n"
" <t t-set=\"colors\" t-value=\"{'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}\"></t>\n"
" <t t-set=\"customer\" t-value=\" object.event_id.find_partner_customer()\"></t>\n"
" <t t-set=\"target_responsible\" t-value=\"object.partner_id == object.event_id.partner_id\"></t>\n"
" <t t-set=\"target_customer\" t-value=\"object.partner_id == customer\"></t>\n"
" <t t-set=\"recurrent\" t-value=\"object.recurrence_id and not ctx.get('calendar_template_ignore_recurrence')\"></t>\n"
"\n"
" <p>\n"
" Hello <t t-out=\"object.common_name or ''\">Wood Corner</t>,<br><br>\n"
"\n"
" <t t-if=\"not target_responsible\">\n"
" <t t-out=\"object.event_id.user_id.partner_id.name or ''\">Colleen Diaz</t> invited you for the <strong t-out=\"object.event_id.name or ''\">Follow-up for Project proposal</strong> meeting.\n"
" </t>\n"
" <t t-else=\"\">\n"
" Your meeting <strong t-out=\"object.event_id.name or ''\">Follow-up for Project proposal</strong> has been booked.\n"
" </t>\n"
"\n"
" </p>\n"
" <div style=\"text-align: center; padding: 16px 0px 16px 0px;\">\n"
" <a t-attf-href=\"/calendar/meeting/accept?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\">\n"
" Accept</a>\n"
" <a t-attf-href=\"/calendar/meeting/decline?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\">\n"
" Decline</a>\n"
" <a t-attf-href=\"/calendar/meeting/view?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\">View</a>\n"
" </div>\n"
" <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n"
" <td width=\"130px;\" style=\"min-width: 130px;\">\n"
" <div style=\"border-top-start-radius: 3px; border-top-end-radius: 3px; font-size: 12px; border-collapse: separate; text-align: center; font-weight: bold; color: #ffffff; min-height: 18px; background-color: #875A7B; border: 1px solid #875A7B;\">\n"
" <t t-out=\"format_datetime(dt=object.event_id.start, tz=object.mail_tz if not object.event_id.allday else None, dt_format='EEEE', lang_code=object.env.lang) or ''\">Tuesday</t>\n"
" </div>\n"
" <div style=\"font-size: 48px; min-height: auto; font-weight: bold; text-align: center; color: #5F5F5F; background-color: #F8F8F8; border: 1px solid #875A7B;\">\n"
" <t t-out=\"format_datetime(dt=object.event_id.start, tz=object.mail_tz if not object.event_id.allday else None, dt_format='d', lang_code=object.env.lang) or ''\">4</t>\n"
" </div>\n"
" <div style=\"font-size: 12px; text-align: center; font-weight: bold; color: #ffffff; background-color: #875A7B;\">\n"
" <t t-out=\"format_datetime(dt=object.event_id.start, tz=object.mail_tz if not object.event_id.allday else None, dt_format='MMMM y', lang_code=object.env.lang) or ''\">May 2021</t>\n"
" </div>\n"
" <div style=\"border-collapse: separate; color: #5F5F5F; text-align: center; font-size: 12px; border-bottom-end-radius: 3px; font-weight: bold ; border: 1px solid #875A7B; border-bottom-start-radius: 3px;\">\n"
" <t t-if=\"not object.event_id.allday\">\n"
" <div>\n"
" <t t-out=\"format_time(time=object.event_id.start, tz=object.mail_tz, time_format='short', lang_code=object.env.lang) or ''\">11:00 AM</t>\n"
" </div>\n"
" <t t-if=\"object.mail_tz\">\n"
" <div style=\"font-size: 10px; font-weight: normal\">\n"
" (<t t-out=\"object.mail_tz or ''\">Europe/Brussels</t>)\n"
" </div>\n"
" </t>\n"
" </t>\n"
" </div>\n"
" </td>\n"
" <td width=\"20px;\"></td>\n"
" <td style=\"padding-top: 5px;\">\n"
" <p><strong>Details of the event</strong></p>\n"
" <ul>\n"
" <t t-if=\"object.event_id.location\">\n"
" <li>Location: <t t-out=\"object.event_id.location or ''\">Bruxelles</t>\n"
" (<a target=\"_blank\" t-attf-href=\"http://maps.google.com/maps?oi=map&amp;q={{object.event_id.location}}\">View Map</a>)\n"
" </li>\n"
" </t>\n"
" <t t-if=\"recurrent\">\n"
" <li>When: <t t-out=\"object.recurrence_id.name or ''\">Every 1 Weeks, for 3 events</t></li>\n"
" </t>\n"
" <t t-if=\"not object.event_id.allday and object.event_id.duration\">\n"
" <li>Duration: <t t-out=\"('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''\">0H30</t></li>\n"
" </t>\n"
" <li>Attendees\n"
" <ul>\n"
" <li t-foreach=\"object.event_id.attendee_ids\" t-as=\"attendee\">\n"
" <div t-attf-style=\"display: inline-block; border-radius: 50%; width: 10px; height: 10px; background:{{ colors.get(attendee.state) or 'white' }};\"> </div>\n"
" <t t-if=\"attendee.common_name != object.common_name\">\n"
" <span style=\"margin-left:5px\" t-out=\"attendee.common_name or ''\">Mitchell Admin</span>\n"
" </t>\n"
" <t t-else=\"\">\n"
" <span style=\"margin-left:5px\">You</span>\n"
" </t>\n"
" </li>\n"
" </ul></li>\n"
" <t t-if=\"object.event_id.videocall_location\">\n"
" <li>\n"
" How to Join:\n"
" <t t-if=\"object.get_base_url() in object.event_id.videocall_location\"> Join with Odoo Discuss</t>\n"
" <t t-else=\"\"> Join at</t><br>\n"
" <a t-att-href=\"object.event_id.videocall_location\" target=\"_blank\" t-out=\"object.event_id.videocall_location or ''\">www.mycompany.com/calendar/join_videocall/xyz</a>\n"
" </li>\n"
" </t>\n"
" <t t-if=\"not is_html_empty(object.event_id.description)\">\n"
" <li>Description of the event:\n"
" <t t-out=\"object.event_id.description\">Internal meeting for discussion for new pricing for product and services.</t></li>\n"
" </t>\n"
" </ul>\n"
" </td>\n"
" </tr></table>\n"
" <br>\n"
" Thank you,\n"
" <t t-if=\"object.event_id.user_id.signature\">\n"
" <br>\n"
" <t t-out=\"object.event_id.user_id.signature or ''\">--<br>Mitchell Admin</t>\n"
" </t>\n"
"</div>\n"
" "
msgstr ""
#. module: calendar
#: model:mail.template,body_html:calendar.calendar_template_meeting_update
msgid ""
@@ -334,7 +230,7 @@ msgid ""
" </li>\n"
" </t>\n"
" <t t-if=\"recurrent\">\n"
" <li>When: <t t-out=\"object.recurrence_id.name or ''\">Every 1 Weeks, for 3 events</t></li>\n"
" <li>When: <t t-out=\"object.recurrence_id.get_recurrence_name()\">Every 1 Weeks, for 3 events</t></li>\n"
" </t>\n"
" <t t-if=\"not object.allday and object.duration\">\n"
" <li>Duration:\n"
@@ -359,6 +255,141 @@ msgid ""
" "
msgstr ""
#. module: calendar
#: model:mail.template,body_html:calendar.calendar_template_meeting_invitation
msgid ""
"<div>\n"
" <t t-set=\"colors\" t-value=\"{'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}\"></t>\n"
" <t t-set=\"is_online\" t-value=\"'appointment_type_id' in object.event_id and object.event_id.appointment_type_id\"></t>\n"
" <t t-set=\"customer\" t-value=\" object.event_id.find_partner_customer()\"></t>\n"
" <t t-set=\"target_responsible\" t-value=\"object.partner_id == object.event_id.partner_id\"></t>\n"
" <t t-set=\"target_customer\" t-value=\"object.partner_id == customer\"></t>\n"
" <t t-set=\"recurrent\" t-value=\"object.recurrence_id and not ctx.get('calendar_template_ignore_recurrence')\"></t>\n"
"\n"
" <p>\n"
" Hello <t t-out=\"object.common_name or ''\">Wood Corner</t>,<br><br>\n"
"\n"
" <t t-if=\"is_online and target_customer\">\n"
" Your appointment <strong t-out=\"object.event_id.appointment_type_id.name or ''\">Schedule a Demo</strong> <t t-if=\"object.event_id.appointment_type_id.category != 'custom'\"> with <t t-out=\"object.event_id.user_id.name or ''\">Ready Mat</t></t> has been booked.\n"
" <t t-if=\"is_online and object.state != 'accepted' and object.event_id.appointment_type_id.resource_manual_confirmation\">\n"
" You will receive a mail of confirmation with more details when your appointment will be confirmed.\n"
" </t>\n"
" </t>\n"
" <t t-elif=\"is_online and target_responsible\">\n"
" <t t-if=\"customer\">\n"
" <t t-out=\"customer.name or ''\"></t> scheduled the following appointment <strong t-out=\"object.event_id.appointment_type_id.name or ''\">Schedule a Demo</strong> with you.\n"
" </t>\n"
" <t t-else=\"\">\n"
" Your appointment <strong t-out=\"object.event_id.appointment_type_id.name or ''\">Schedule a Demo</strong> has been booked.\n"
" </t>\n"
" </t>\n"
" <t t-elif=\"not target_responsible\">\n"
" <t t-out=\"object.event_id.user_id.partner_id.name or ''\">Colleen Diaz</t> invited you for the <strong t-out=\"object.event_id.name or ''\">Follow-up for Project proposal</strong> meeting.\n"
" </t>\n"
" <t t-else=\"\">\n"
" Your meeting <strong t-out=\"object.event_id.name or ''\">Follow-up for Project proposal</strong> has been booked.\n"
" </t>\n"
"\n"
" </p>\n"
" <div style=\"text-align: center; padding: 16px 0px 16px 0px;\">\n"
" <t t-if=\"not is_online or object.state != 'accepted'\">\n"
" <a t-attf-href=\"/calendar/meeting/accept?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\">\n"
" Accept</a>\n"
" <a t-attf-href=\"/calendar/meeting/decline?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\">\n"
" Decline</a>\n"
" </t>\n"
" <a t-attf-href=\"/calendar/meeting/view?token={{object.access_token}}&amp;id={{object.event_id.id}}\" style=\"padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px\"><t t-out=\"'Reschedule' if is_online and target_customer else 'View'\">View</t></a>\n"
" </div>\n"
" <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n"
" <td width=\"130px;\" style=\"min-width: 130px;\">\n"
" <div style=\"border-top-start-radius: 3px; border-top-end-radius: 3px; font-size: 12px; border-collapse: separate; text-align: center; font-weight: bold; color: #ffffff; min-height: 18px; background-color: #875A7B; border: 1px solid #875A7B;\">\n"
" <t t-out=\"format_datetime(dt=object.event_id.start, tz=object.mail_tz if not object.event_id.allday else None, dt_format='EEEE', lang_code=object.env.lang) or ''\">Tuesday</t>\n"
" </div>\n"
" <div style=\"font-size: 48px; min-height: auto; font-weight: bold; text-align: center; color: #5F5F5F; background-color: #F8F8F8; border: 1px solid #875A7B;\">\n"
" <t t-out=\"str(object.event_id.start.day) or ''\">4</t>\n"
" </div>\n"
" <div style=\"font-size: 12px; text-align: center; font-weight: bold; color: #ffffff; background-color: #875A7B;\">\n"
" <t t-out=\"format_datetime(dt=object.event_id.start, tz=object.mail_tz if not object.event_id.allday else None, dt_format='MMMM y', lang_code=object.env.lang) or ''\">May 2021</t>\n"
" </div>\n"
" <div style=\"border-collapse: separate; color: #5F5F5F; text-align: center; font-size: 12px; border-bottom-end-radius: 3px; font-weight: bold ; border: 1px solid #875A7B; border-bottom-start-radius: 3px;\">\n"
" <t t-if=\"not object.event_id.allday\">\n"
" <div>\n"
" <t t-out=\"format_time(time=object.event_id.start, tz=object.mail_tz, time_format='short', lang_code=object.env.lang) or ''\">11:00 AM</t>\n"
" </div>\n"
" <t t-if=\"object.mail_tz\">\n"
" <div style=\"font-size: 10px; font-weight: normal\">\n"
" (<t t-out=\"object.mail_tz or ''\">Europe/Brussels</t>)\n"
" </div>\n"
" </t>\n"
" </t>\n"
" </div>\n"
" </td>\n"
" <td width=\"20px;\"></td>\n"
" <td style=\"padding-top: 5px;\">\n"
" <p><strong>Details of the event</strong></p>\n"
" <ul>\n"
" <t t-if=\"is_online\">\n"
" <li>Appointment Type: <t t-out=\"object.event_id.appointment_type_id.name or ''\">Schedule a Demo</t></li>\n"
" </t>\n"
" <t t-if=\"object.event_id.location\">\n"
" <li>Location: <t t-out=\"object.event_id.location or ''\">Bruxelles</t>\n"
" (<a target=\"_blank\" t-attf-href=\"http://maps.google.com/maps?oi=map&amp;q={{object.event_id.location}}\">View Map</a>)\n"
" </li>\n"
" </t>\n"
" <t t-if=\"recurrent\">\n"
" <li>When: <t t-out=\"object.recurrence_id.get_recurrence_name()\">Every 1 Weeks, for 3 events</t></li>\n"
" </t>\n"
" <t t-if=\"not object.event_id.allday and object.event_id.duration\">\n"
" <li>Duration: <t t-out=\"('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''\">0H30</t></li>\n"
" </t>\n"
" <li>Attendees\n"
" <ul>\n"
" <li t-foreach=\"object.event_id.attendee_ids\" t-as=\"attendee\">\n"
" <div t-attf-style=\"display: inline-block; border-radius: 50%; width: 10px; height: 10px; background:{{ colors.get(attendee.state) or 'white' }};\"> </div>\n"
" <t t-if=\"attendee.common_name != object.common_name\">\n"
" <span style=\"margin-left:5px\" t-out=\"attendee.common_name or ''\">Mitchell Admin</span>\n"
" </t>\n"
" <t t-else=\"\">\n"
" <span style=\"margin-left:5px\">You</span>\n"
" </t>\n"
" </li>\n"
" </ul></li>\n"
" <li t-if=\"is_online and object.event_id.appointment_type_id.resource_manage_capacity\">\n"
" For: <t t-out=\"object.event_id.resource_total_capacity_reserved\"></t> people\n"
" </li>\n"
" <li t-if=\"is_online and object.event_id.appointment_type_id.assign_method != 'time_auto_assign'\">\n"
" Resources\n"
" <ul>\n"
" <li t-foreach=\"object.event_id.appointment_resource_ids\" t-as=\"resource\">\n"
" <span style=\"margin-left:5px\" t-out=\"resource.name or ''\">Table 1</span>\n"
" </li>\n"
" </ul>\n"
" </li>\n"
" <t t-if=\"object.event_id.videocall_location\">\n"
" <li>\n"
" How to Join:\n"
" <t t-if=\"object.get_base_url() in object.event_id.videocall_location\"> Join with Odoo Discuss</t>\n"
" <t t-else=\"\"> Join at</t><br>\n"
" <a t-att-href=\"object.event_id.videocall_location\" target=\"_blank\" t-out=\"object.event_id.videocall_location or ''\">www.mycompany.com/calendar/join_videocall/xyz</a>\n"
" </li>\n"
" </t>\n"
" <t t-if=\"not is_html_empty(object.event_id.description)\">\n"
" <li>Description of the event:\n"
" <t t-out=\"object.event_id.description\">Internal meeting for discussion for new pricing for product and services.</t></li>\n"
" </t>\n"
" </ul>\n"
" </td>\n"
" </tr></table>\n"
" <br>\n"
" Thank you,\n"
" <t t-if=\"object.event_id.user_id.signature\">\n"
" <br>\n"
" <t t-out=\"object.event_id.user_id.signature or ''\">--<br>Mitchell Admin</t>\n"
" </t>\n"
"</div>\n"
" "
msgstr ""
#. module: calendar
#: model:mail.template,body_html:calendar.calendar_template_meeting_reminder
msgid ""
@@ -412,7 +443,7 @@ msgid ""
" </li>\n"
" </t>\n"
" <t t-if=\"recurrent\">\n"
" <li>When: <t t-out=\"object.recurrence_id.name or ''\">Every 1 Weeks, for 3 events</t></li>\n"
" <li>When: <t t-out=\"object.recurrence_id.get_recurrence_name()\">Every 1 Weeks, for 3 events</t></li>\n"
" </t>\n"
" <t t-if=\"not object.event_id.allday and object.event_id.duration\">\n"
" <li>Duration: <t t-out=\"('%dH%02d' % (object.event_id.duration,round(object.event_id.duration*60)%60)) or ''\">0H30</t></li>\n"
@@ -1196,7 +1227,108 @@ msgstr ""
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(count)s %(period)s"
msgid "Every %(interval)s Days"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Days for %(count)s events"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Days until %(until)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Months day %(day)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Months day %(day)s for %(count)s events"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Months day %(day)s until %(until)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Months on the %(position)s %(weekday)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid ""
"Every %(interval)s Months on the %(position)s %(weekday)s for %(count)s "
"events"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid ""
"Every %(interval)s Months on the %(position)s %(weekday)s until %(until)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Weeks on %(days)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Weeks on %(days)s for %(count)s events"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Weeks on %(days)s until %(until)s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Years"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Years for %(count)s events"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "Every %(interval)s Years until %(until)s"
msgstr ""
#. module: calendar
@@ -2400,13 +2532,6 @@ msgstr ""
msgid "awaiting"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "day %s"
msgstr ""
#. module: calendar
#. odoo-javascript
#: code:addons/calendar/static/src/views/fields/many2many_attendee_expandable.xml:0
@@ -2442,24 +2567,8 @@ msgid "no,"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "on %s"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "on the %(position)s %(weekday)s"
msgstr ""
#. module: calendar
#. odoo-javascript
#: code:addons/calendar/static/src/views/fields/many2many_attendee.xml:0
#, python-format
msgid "props.placeholder"
#: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form
msgid "to"
msgstr ""
#. module: calendar
@@ -2469,13 +2578,6 @@ msgstr ""
msgid "uncertain"
msgstr ""
#. module: calendar
#. odoo-python
#: code:addons/calendar/models/calendar_recurrence.py:0
#, python-format
msgid "until %s"
msgstr ""
#. module: calendar
#: model_terms:ir.ui.view,arch_db:calendar.view_calendar_event_form
msgid "yes,"
+61 -29
View File
@@ -131,38 +131,70 @@ class RecurrenceRule(models.Model):
"The day must be between 1 and 31"),
]
def _get_daily_recurrence_name(self):
if self.end_type == 'count':
return _("Every %(interval)s Days for %(count)s events", interval=self.interval, count=self.count)
if self.end_type == 'end_date':
return _("Every %(interval)s Days until %(until)s", interval=self.interval, until=self.until)
return _("Every %(interval)s Days", interval=self.interval)
def _get_weekly_recurrence_name(self):
weekday_selection = dict(self._fields['weekday']._description_selection(self.env))
weekdays = self._get_week_days()
# Convert Weekday object
weekdays = [str(w) for w in weekdays]
# We need to get the day full name from its three first letters.
week_map = {v: k for k, v in RRULE_WEEKDAYS.items()}
weekday_short = [week_map[w] for w in weekdays]
day_strings = [weekday_selection[day] for day in weekday_short]
days = ", ".join(day_strings)
if self.end_type == 'count':
return _("Every %(interval)s Weeks on %(days)s for %(count)s events", interval=self.interval, days=days, count=self.count)
if self.end_type == 'end_date':
return _("Every %(interval)s Weeks on %(days)s until %(until)s", interval=self.interval, days=days, until=self.until)
return _("Every %(interval)s Weeks on %(days)s", interval=self.interval, days=days)
def _get_monthly_recurrence_name(self):
if self.month_by == 'day':
weekday_selection = dict(self._fields['weekday']._description_selection(self.env))
byday_selection = dict(self._fields['byday']._description_selection(self.env))
position_label = byday_selection[self.byday]
weekday_label = weekday_selection[self.weekday]
if self.end_type == 'count':
return _("Every %(interval)s Months on the %(position)s %(weekday)s for %(count)s events", interval=self.interval, position=position_label, weekday=weekday_label, count=self.count)
if self.end_type == 'end_date':
return _("Every %(interval)s Months on the %(position)s %(weekday)s until %(until)s", interval=self.interval, position=position_label, weekday=weekday_label, until=self.until)
return _("Every %(interval)s Months on the %(position)s %(weekday)s", interval=self.interval, position=position_label, weekday=weekday_label)
else:
if self.end_type == 'count':
return _("Every %(interval)s Months day %(day)s for %(count)s events", interval=self.interval, day=self.day, count=self.count)
if self.end_type == 'end_date':
return _("Every %(interval)s Months day %(day)s until %(until)s", interval=self.interval, day=self.day, until=self.until)
return _("Every %(interval)s Months day %(day)s", interval=self.interval, day=self.day)
def _get_yearly_recurrence_name(self):
if self.end_type == 'count':
return _("Every %(interval)s Years for %(count)s events", interval=self.interval, count=self.count)
if self.end_type == 'end_date':
return _("Every %(interval)s Years until %(until)s", interval=self.interval, until=self.until)
return _("Every %(interval)s Years", interval=self.interval)
def get_recurrence_name(self):
if self.rrule_type == 'daily':
return self._get_daily_recurrence_name()
if self.rrule_type == 'weekly':
return self._get_weekly_recurrence_name()
if self.rrule_type == 'monthly':
return self._get_monthly_recurrence_name()
if self.rrule_type == 'yearly':
return self._get_yearly_recurrence_name()
@api.depends('rrule')
def _compute_name(self):
for recurrence in self:
period = dict(RRULE_TYPE_SELECTION)[recurrence.rrule_type]
every = _("Every %(count)s %(period)s", count=recurrence.interval, period=period)
if recurrence.end_type == 'count':
end = _("for %s events", recurrence.count)
elif recurrence.end_type == 'end_date':
end = _("until %s", recurrence.until)
else:
end = ''
if recurrence.rrule_type == 'weekly':
weekdays = recurrence._get_week_days()
# Convert Weekday object
weekdays = [str(w) for w in weekdays]
# We need to get the day full name from its three first letters.
week_map = {v: k for k, v in RRULE_WEEKDAYS.items()}
weekday_short = [week_map[w] for w in weekdays]
day_strings = [d[1] for d in WEEKDAY_SELECTION if d[0] in weekday_short]
on = _("on %s", ", ".join(day_strings))
elif recurrence.rrule_type == 'monthly':
if recurrence.month_by == 'day':
position_label = dict(BYDAY_SELECTION)[recurrence.byday]
weekday_label = dict(WEEKDAY_SELECTION)[recurrence.weekday]
on = _("on the %(position)s %(weekday)s", position=position_label, weekday=weekday_label)
else:
on = _("day %s", recurrence.day)
else:
on = ''
recurrence.name = ' '.join(filter(lambda s: s, [every, on, end]))
recurrence.name = recurrence.get_recurrence_name()
@api.depends('calendar_event_ids.start')
def _compute_dtstart(self):
+1
View File
@@ -10,3 +10,4 @@ from . import test_event_recurrence
from . import test_event_notifications
from . import test_mail_activity_mixin
from . import test_res_partner
from . import test_recurrence_rule
@@ -0,0 +1,175 @@
from datetime import datetime
from odoo.tests.common import TransactionCase
class TestRecurrenceRule(TransactionCase):
def test_daily_count(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days for 3 events')
def test_daily_until(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days until 2024-11-15')
def test_daily_none(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'daily',
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Days')
def test_weekly_count(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday for 3 events')
def test_weekly_until(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday until 2024-11-15')
def test_weekly_none(self):
""" Every week, on Tuesdays, for 3 occurences """
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'weekly',
'tue': True,
'wed': True,
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Weeks on Tuesday, Wednesday')
def test_monthly_count_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday for 3 events')
def test_monthly_until_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday until 2024-11-15')
def test_monthly_none_by_day(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'day',
'byday': '1',
'weekday': 'MON',
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months on the First Monday')
def test_monthly_count_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': 'count',
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27 for 3 events')
def test_monthly_until_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27 until 2024-11-15')
def test_monthly_none_by_date(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'monthly',
'interval': 2,
'month_by': 'date',
'day': 27,
'weekday': 'MON',
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Months day 27')
def test_yearly_count(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'count': 3,
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years for 3 events')
def test_yearly_until(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'end_type': 'end_date',
'until': datetime(2024, 11, 15),
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years until 2024-11-15')
def test_yearly_none(self):
recurrence = self.env['calendar.recurrence'].create({
'rrule_type': 'yearly',
'interval': 2,
'end_type': '',
'event_tz': 'UTC',
})
self.assertEqual(recurrence.name, 'Every 2 Years')