Add calendar event recurrence rule translations (#14544)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Philip Allgaier 2022-12-05 18:28:48 +01:00 committed by GitHub
parent f0f699a37e
commit ecdd07ff4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 15 deletions

View File

@ -7,10 +7,12 @@ if (__BUILD__ === "latest" && polyfillsLoaded) {
} }
// Tuesday, August 10 // Tuesday, August 10
export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) => export const formatDateWeekdayDay = (
formatDateWeekdayMem(locale).format(dateObj); dateObj: Date,
locale: FrontendLocaleData
) => formatDateWeekdayDayMem(locale).format(dateObj);
const formatDateWeekdayMem = memoizeOne( const formatDateWeekdayDayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
weekday: "long", weekday: "long",
@ -92,3 +94,14 @@ const formatDateYearMem = memoizeOne(
year: "numeric", year: "numeric",
}) })
); );
// Monday
export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj);
const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
})
);

View File

@ -0,0 +1,10 @@
import { addDays, startOfWeek } from "date-fns";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { formatDateWeekday } from "../datetime/format_date";
export const dayNames = memoizeOne((locale: FrontendLocaleData): string[] =>
Array.from({ length: 7 }, (_, d) =>
formatDateWeekday(addDays(startOfWeek(new Date()), d), locale)
)
);

View File

@ -18,6 +18,7 @@ export type LocalizeKeys =
| `ui.card.alarm_control_panel.${string}` | `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}` | `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}` | `ui.card.weather.cardinal_direction.${string}`
| `ui.components.calendar.event.rrule.${string}`
| `ui.components.logbook.${string}` | `ui.components.logbook.${string}`
| `ui.components.selectors.file.${string}` | `ui.components.selectors.file.${string}`
| `ui.dialogs.entity_registry.editor.${string}` | `ui.dialogs.entity_registry.editor.${string}`

View File

@ -0,0 +1,10 @@
import { addMonths, startOfYear } from "date-fns";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { formatDateMonth } from "../datetime/format_date";
export const monthNames = memoizeOne((locale: FrontendLocaleData): string[] =>
Array.from({ length: 12 }, (_, m) =>
formatDateMonth(addMonths(startOfYear(new Date()), m), locale)
)
);

View File

@ -40,7 +40,7 @@ import {
formatDateMonth, formatDateMonth,
formatDateMonthYear, formatDateMonthYear,
formatDateShort, formatDateShort,
formatDateWeekday, formatDateWeekdayDay,
formatDateYear, formatDateYear,
} from "../../common/datetime/format_date"; } from "../../common/datetime/format_date";
import { import {
@ -92,7 +92,7 @@ _adapters._date.override({
case "hour": case "hour":
return formatTime(new Date(time), this.options.locale); return formatTime(new Date(time), this.options.locale);
case "weekday": case "weekday":
return formatDateWeekday(new Date(time), this.options.locale); return formatDateWeekdayDay(new Date(time), this.options.locale);
case "date": case "date":
return formatDate(new Date(time), this.options.locale); return formatDate(new Date(time), this.options.locale);
case "day": case "day":

View File

@ -14,7 +14,7 @@ import {
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { formatDateWeekday } from "../../../common/datetime/format_date"; import { formatDateWeekdayDay } from "../../../common/datetime/format_date";
import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatTimeWeekday } from "../../../common/datetime/format_time";
import { formatNumber } from "../../../common/number/format_number"; import { formatNumber } from "../../../common/number/format_number";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -170,7 +170,7 @@ class MoreInfoWeather extends LitElement {
` `
: html` : html`
<div class="main"> <div class="main">
${formatDateWeekday( ${formatDateWeekdayDay(
new Date(item.datetime), new Date(item.datetime),
this.hass.locale this.hass.locale
)} )}

View File

@ -3,13 +3,15 @@ import { mdiCalendarClock, mdiClose } from "@mdi/js";
import { addDays, isSameDay } from "date-fns/esm"; import { addDays, isSameDay } from "date-fns/esm";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import { RRule } from "rrule"; import { RRule, Weekday } from "rrule";
import { formatDate } from "../../common/datetime/format_date"; import { formatDate } from "../../common/datetime/format_date";
import { formatDateTime } from "../../common/datetime/format_date_time"; import { formatDateTime } from "../../common/datetime/format_date_time";
import { formatTime } from "../../common/datetime/format_time"; import { formatTime } from "../../common/datetime/format_time";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { capitalizeFirstLetter } from "../../common/string/capitalize-first-letter"; import { capitalizeFirstLetter } from "../../common/string/capitalize-first-letter";
import { isDate } from "../../common/string/is_date"; import { isDate } from "../../common/string/is_date";
import { dayNames } from "../../common/translations/day_names";
import { monthNames } from "../../common/translations/month_names";
import "../../components/entity/state-info"; import "../../components/entity/state-info";
import "../../components/ha-date-input"; import "../../components/ha-date-input";
import "../../components/ha-time-input"; import "../../components/ha-time-input";
@ -85,7 +87,7 @@ class DialogCalendarEventDetail extends LitElement {
<div class="value"> <div class="value">
${this._formatDateRange()}<br /> ${this._formatDateRange()}<br />
${this._data!.rrule ${this._data!.rrule
? this._renderRruleAsText(this._data.rrule) ? this._renderRRuleAsText(this._data.rrule)
: ""} : ""}
${this._data.description ${this._data.description
? html`<br /> ? html`<br />
@ -128,20 +130,59 @@ class DialogCalendarEventDetail extends LitElement {
`; `;
} }
private _renderRruleAsText(value: string) { private _renderRRuleAsText(value: string) {
// TODO: Make sure this handles translations
if (!value) { if (!value) {
return ""; return "";
} }
try { try {
return html`<div id="text"> const rule = RRule.fromString(`RRULE:${value}`);
${capitalizeFirstLetter(RRule.fromString(`RRULE:${value}`).toText())} if (rule.isFullyConvertibleToText()) {
</div>`; return html`<div id="text">
${capitalizeFirstLetter(
rule.toText(
this._translateRRuleElement,
{
dayNames: dayNames(this.hass.locale),
monthNames: monthNames(this.hass.locale),
tokens: {},
},
this._formatDate
)
)}
</div>`;
}
return html`<div id="text">Cannot convert recurrence rule</div>`;
} catch (e) { } catch (e) {
return ""; return "Error while processing the rule";
} }
} }
private _translateRRuleElement = (id: string | number | Weekday): string => {
if (typeof id === "string") {
return this.hass.localize(`ui.components.calendar.event.rrule.${id}`);
}
return "";
};
private _formatDate = (year: number, month: string, day: number): string => {
if (!year || !month || !day) {
return "";
}
// Build date so we can then format it
const date = new Date();
date.setFullYear(year);
// As input we already get the localized month name, so we now unfortunately
// need to convert it back to something Date can work with. The already localized
// months names are a must in the RRule.Language structure (an empty string[] would
// mean we get undefined months input in this method here).
date.setMonth(monthNames(this.hass.locale).indexOf(month));
date.setDate(day);
return formatDate(date, this.hass.locale);
};
private _formatDateRange() { private _formatDateRange() {
const start = new Date(this._data!.dtstart); const start = new Date(this._data!.dtstart);
// All day events should be displayed as a day earlier // All day events should be displayed as a day earlier

View File

@ -330,6 +330,13 @@ class DialogCalendarEventEditor extends LitElement {
} }
private async _createEvent() { private async _createEvent() {
if (!this._summary || !this._calendarId) {
this._error = this.hass.localize(
"ui.components.calendar.event.not_all_required_fields"
);
return;
}
this._submitting = true; this._submitting = true;
try { try {
await createCalendarEvent( await createCalendarEvent(
@ -418,6 +425,10 @@ class DialogCalendarEventEditor extends LitElement {
state-info { state-info {
line-height: 40px; line-height: 40px;
} }
ha-alert {
display: block;
margin-bottom: 16px;
}
ha-textfield, ha-textfield,
ha-textarea { ha-textarea {
display: block; display: block;

View File

@ -642,6 +642,7 @@
"all_day": "All Day", "all_day": "All Day",
"start": "Start", "start": "Start",
"end": "End", "end": "End",
"not_all_required_fields": "Not all required fields are filled in.",
"confirm_delete": { "confirm_delete": {
"delete": "Delete Event", "delete": "Delete Event",
"delete_this": "Delete Only This Event", "delete_this": "Delete Only This Event",
@ -666,6 +667,30 @@
"daily": "days" "daily": "days"
} }
}, },
"rrule": {
"every": "every",
"years": "years",
"year": "year",
"months": "months",
"month": "month",
"weeks": "weeks",
"week": "week",
"weekdays": "weekdays",
"weekday": "weekday",
"days": "days",
"day": "day",
"until": "until",
"for": "for",
"in": "in",
"on": "on",
"on the": "on the",
"and": "and",
"or": "or",
"at": "at",
"last": "last",
"time": "time",
"times": "times"
},
"summary": "Summary", "summary": "Summary",
"description": "Description" "description": "Description"
} }