Skip to content

Fix calendar event recurrence UI in non-English locales - #2657

Open
qiyana-ratchet wants to merge 1 commit into
instructure:masterfrom
qiyana-ratchet:fix/calendar-recurrence-non-english-locales
Open

Fix calendar event recurrence UI in non-English locales#2657
qiyana-ratchet wants to merge 1 commit into
instructure:masterfrom
qiyana-ratchet:fix/calendar-recurrence-non-english-locales

Conversation

@qiyana-ratchet

@qiyana-ratchet qiyana-ratchet commented Aug 6, 2026

Copy link
Copy Markdown

Two defects in the calendar event recurrence UI only reproduce when the user
locale is not English, which is why they have gone unnoticed. Both are still
present on master as of today.

1. The frequency picker fails to render for recurring events

Opening the "More Options" page for an event that has a recurrence renders an
error block where the frequency picker should be:

There was an error rendering.
FrequencyPicker: date is required when initialFrequency is not not-repeat

The page never recovers, because the error boundary has no reset path. The event
is still editable, but the recurrence can no longer be seen or changed.

EditEventView writes the start date into the date field with the active
locale, and then reads it back with a hard coded English pattern:

// written with the active locale
picked_params.start_date = new Intl.DateTimeFormat(ENV.LOCALE, {
  month: 'short',
  day: 'numeric',
  year: 'numeric',
  timeZone: ENV.TIMEZONE,
}).format(new Date(picked_params.start_at))

// read back as English only
const eventStart = start ? moment.tz(start, 'MMM D, YYYY', ENV.TIMEZONE) : moment('invalid')

In English the two formats happen to agree, so the round trip works. In ko
the field holds 2026년 3월 6일, the English pattern fails to parse it, and
date becomes undefined. FrequencyPicker deliberately throws when it has no
date and a frequency other than not-repeat, so the error surfaces only for
events that repeat. Events without a recurrence never meet that throw
condition, which is why the page looks fine for them.

The same round trip is done in three places in EditEventView.jsx: computing
the initial frequency, rendering the frequency picker, and handling start date
changes.

Approach. Parse with the locale aware parser instead of a fixed pattern.
tz.parse from @instructure/moment-utils already resolves both the Moment
locale formats and the Canvas i18n date formats, and it is what the date field
itself (DatetimeField#parseValue) uses. Routing all three call sites through
one helper makes the view agree with the field it reads from, and removes the
locale assumption rather than adding another format to the list.

A second, related case is fixed as well. Clearing the start date on an event
that repeats produces the identical error in every locale, because date
becomes undefined while initialFrequency is still a repeating value. While
the start date is unknown the picker is now rendered disabled and non
repeating, and its change notifications are ignored so the stored rrule is
not silently reset to not-repeat and lost on save.

2. The "Ends:" labels stack one character per line

In the custom recurrence modal, the On and After radio labels are laid out
in a grid whose columns are sized min-content:

gridTemplateColumns: 'min-content min-content',

min-content resolves to the narrowest wrappable width. English words do not
break mid-word, so the whole word becomes that minimum and the label stays on
one line. CJK text allows breaks between characters, so the minimum is a single
character and the column collapses to that width, stacking the label
vertically. In Korean the labels read as one character per row.

Approach. Size the label column max-content so it is as wide as the label
needs. Capping the break from the outside with white-space: nowrap does not
work here, because the InstUI radio label element resets inherited properties
with all: initial. Only the two labels live in that column, so widening it
does not affect the rest of the modal.

I am happy to reshape this however the review prefers. If you take the fixes, I would
appreciate having this commit merged rather than reimplemented, and if it has to land
through your internal review instead, please consider keeping attribution with:

Co-authored-by: Taehyun Kim <81336710+qiyana-ratchet@users.noreply.github.com>

Test Plan:

  • Set your user language to a non-English locale whose date format differs
    from English. Korean is a good case because 2026년 3월 6일 shares no
    tokens with MMM D, YYYY, and its labels are short enough to make the
    stacking obvious.
  • Go to the calendar, click a day, and fill in a title and a date.
  • Set Frequency to a repeating value, for example Weekly.
  • Click "More Options".
  • Verify the frequency picker renders with the frequency you selected, and
    that no "There was an error rendering." block is present.
  • Change the start date and verify the frequency picker labels update to the
    new date, for example the weekday named in the weekly option.
  • Clear the start date entirely. Verify the picker becomes disabled and shows
    "Does not repeat", and that no error block appears.
  • Re-enter a start date. Verify the picker becomes selectable again.
  • Open the frequency picker and choose "Custom" to open the recurrence modal.
    Verify the "Ends:" radio labels each render on a single line.
  • Save the event and verify the recurring events are created on the expected
    dates.
  • Repeat the whole flow with the user language set to English and verify
    nothing changed from current behavior, including the modal layout.
  • Also verify an event without any recurrence still opens "More Options"
    normally in both locales.
image

Two defects in the calendar event recurrence UI only reproduce when the user
locale is not English.

First, opening "More Options" for an event that has a recurrence renders an
error block instead of the frequency picker, and the page never recovers.
EditEventView writes the start date into the date field with the active locale
via Intl.DateTimeFormat(ENV.LOCALE), then reads it back with the hard coded
English pattern 'MMM D, YYYY'. In English the two formats happen to agree, so
the round trip works. In ko the field holds "2026년 3월 6일", the English
pattern fails to parse it, and date becomes undefined. FrequencyPicker
deliberately throws when it has no date and a frequency other than not-repeat,
so the error surfaces only for events that repeat.

Parsing now goes through tz.parse from @instructure/moment-utils, which already
resolves both the Moment locale formats and the Canvas i18n date formats and is
what DatetimeField itself uses. Routing all three call sites through one helper
makes the view agree with the field it reads from, and removes the locale
assumption rather than adding another format to the list. Clearing the start
date hits the same throw condition in every locale, so while the date is
unknown the picker is rendered disabled and non repeating, and its change
notifications are ignored so the stored rrule is not silently reset.

Second, the "Ends:" radio labels in the custom recurrence modal stack one
character per line. Their grid column is sized min-content, which resolves to
the narrowest wrappable width. English words do not break mid-word, so the word
itself becomes that minimum, but CJK text allows breaks between characters and
the column collapses to a single character. The label column is now max-content.
Capping the break from the outside does not work here because the InstUI radio
label resets inherited properties with all: initial.

Test Plan:
  - Set your user language to a non-English locale whose date format differs
    from English. Korean is a good case because "2026년 3월 6일" shares no
    tokens with 'MMM D, YYYY', and its labels are short enough to make the
    stacking obvious.
  - Go to the calendar, click a day, and fill in a title and a date.
  - Set Frequency to a repeating value, for example Weekly.
  - Click "More Options".
  - Verify the frequency picker renders with the frequency you selected, and
    that no "There was an error rendering." block is present.
  - Change the start date and verify the frequency picker labels update to the
    new date, for example the weekday named in the weekly option.
  - Clear the start date entirely. Verify the picker becomes disabled and shows
    "Does not repeat", and that no error block appears.
  - Re-enter a start date. Verify the picker becomes selectable again.
  - Open the frequency picker and choose "Custom" to open the recurrence modal.
    Verify the "Ends:" radio labels each render on a single line.
  - Save the event and verify the recurring events are created on the expected
    dates.
  - Repeat the whole flow with the user language set to English and verify
    nothing changed from current behavior, including the modal layout.
  - Also verify an event without any recurrence still opens "More Options"
    normally in both locales.
@CLAassistant

CLAassistant commented Aug 6, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants