mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add calendar event recurrence rule translations (#14544)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
f0f699a37e
commit
ecdd07ff4d
@ -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",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
10
src/common/translations/day_names.ts
Normal file
10
src/common/translations/day_names.ts
Normal 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)
|
||||||
|
)
|
||||||
|
);
|
@ -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}`
|
||||||
|
10
src/common/translations/month_names.ts
Normal file
10
src/common/translations/month_names.ts
Normal 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)
|
||||||
|
)
|
||||||
|
);
|
@ -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":
|
||||||
|
@ -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
|
||||||
)}
|
)}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user