Add integration name information to repairs (#22006)

* Add integration name to repairs

* Improve dialog-repairs-issue aria and translations

* Fix type in dialog-repairs-issue

* Remove unused slots in dialog-repairs-issue

* Fix ha-config-repairs avoid nested css

* Fix ha-config-repairs to use ha-md-list

* Add subtitle slot to ha-dialog-header

* Move close icon to left in dialog-data-entry-flow

* Move severity and reportedBy to dialog subtitle in repair-dialog

* Add md buttons to dialog-repairs-issue

* Revert dialog-repairs-issue to use normal ha-buttons

* Revert dialog-entry-flow close icon position

* Improve buttons for dialog-repairs-issue

* Add subtitle to all show-dialog-repair-flow headers

* Fix integration names for repair dialogs

* Fix subtitle title repair dialogs

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Wendelin 2024-09-25 16:12:45 +02:00 committed by GitHub
parent cd631e8693
commit 254ee8568b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 263 additions and 145 deletions

View File

@ -10,8 +10,13 @@ export class HaDialogHeader extends LitElement {
<section class="header-navigation-icon"> <section class="header-navigation-icon">
<slot name="navigationIcon"></slot> <slot name="navigationIcon"></slot>
</section> </section>
<section class="header-title"> <section class="header-content">
<slot name="title"></slot> <div class="header-title">
<slot name="title"></slot>
</div>
<div class="header-subtitle">
<slot name="subtitle"></slot>
</div>
</section> </section>
<section class="header-action-items"> <section class="header-action-items">
<slot name="actionItems"></slot> <slot name="actionItems"></slot>
@ -39,17 +44,24 @@ export class HaDialogHeader extends LitElement {
padding: 4px; padding: 4px;
box-sizing: border-box; box-sizing: border-box;
} }
.header-title { .header-content {
flex: 1; flex: 1;
font-size: 22px;
line-height: 28px;
font-weight: 400;
padding: 10px 4px; padding: 10px 4px;
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.header-title {
font-size: 22px;
line-height: 28px;
font-weight: 400;
}
.header-subtitle {
font-size: 14px;
line-height: 20px;
color: var(--secondary-text-color);
}
@media all and (min-width: 450px) and (min-height: 500px) { @media all and (min-width: 450px) and (min-height: 500px) {
.header-bar { .header-bar {
padding: 12px; padding: 12px;

View File

@ -31,6 +31,11 @@ export interface FlowConfig {
deleteFlow(hass: HomeAssistant, flowId: string): Promise<unknown>; deleteFlow(hass: HomeAssistant, flowId: string): Promise<unknown>;
renderAbortHeader?(
hass: HomeAssistant,
step: DataEntryFlowStepAbort
): TemplateResult | string;
renderAbortDescription( renderAbortDescription(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepAbort step: DataEntryFlowStepAbort
@ -39,7 +44,7 @@ export interface FlowConfig {
renderShowFormStepHeader( renderShowFormStepHeader(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepForm step: DataEntryFlowStepForm
): string; ): string | TemplateResult;
renderShowFormStepDescription( renderShowFormStepDescription(
hass: HomeAssistant, hass: HomeAssistant,
@ -95,14 +100,17 @@ export interface FlowConfig {
renderShowFormProgressHeader( renderShowFormProgressHeader(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepProgress step: DataEntryFlowStepProgress
): string; ): string | TemplateResult;
renderShowFormProgressDescription( renderShowFormProgressDescription(
hass: HomeAssistant, hass: HomeAssistant,
step: DataEntryFlowStepProgress step: DataEntryFlowStepProgress
): TemplateResult | ""; ): TemplateResult | "";
renderMenuHeader(hass: HomeAssistant, step: DataEntryFlowStepMenu): string; renderMenuHeader(
hass: HomeAssistant,
step: DataEntryFlowStepMenu
): string | TemplateResult;
renderMenuDescription( renderMenuDescription(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -31,7 +31,11 @@ class StepFlowAbort extends LitElement {
return nothing; return nothing;
} }
return html` return html`
<h2>${this.hass.localize(`component.${this.domain}.title`)}</h2> <h2>
${this.params.flowConfig.renderAbortHeader
? this.params.flowConfig.renderAbortHeader(this.hass, this.step)
: this.hass.localize(`component.${this.domain}.title`)}
</h2>
<div class="content"> <div class="content">
${this.params.flowConfig.renderAbortDescription(this.hass, this.step)} ${this.params.flowConfig.renderAbortDescription(this.hass, this.step)}
</div> </div>

View File

@ -0,0 +1,63 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../../types";
import { domainToName } from "../../../data/integration";
import type { RepairsIssue } from "../../../data/repairs";
@customElement("dialog-repairs-issue-subtitle")
class DialogRepairsIssueSubtitle extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Object }) public issue!: RepairsIssue;
protected firstUpdated() {
if (this.scrollWidth > this.offsetWidth) {
this.title =
(this.shadowRoot?.firstElementChild as HTMLElement)?.innerText || "";
}
}
protected render() {
const domainName = domainToName(this.hass.localize, this.issue.domain);
const reportedBy = domainName
? this.hass.localize("ui.panel.config.repairs.reported_by", {
integration: domainName,
})
: "";
const severity = this.hass.localize(
`ui.panel.config.repairs.${this.issue.severity}`
);
return html`
<span>
<span class=${this.issue.severity}> ${severity} </span>
${reportedBy}
</span>
`;
}
static styles = css`
:host {
display: block;
font-size: 14px;
margin-bottom: 8px;
color: var(--secondary-text-color);
text-overflow: ellipsis;
overflow: hidden;
}
.error,
.critical {
color: var(--error-color);
}
.warning {
color: var(--warning-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"dialog-repairs-issue-subtitle": DialogRepairsIssueSubtitle;
}
}

View File

@ -1,11 +1,14 @@
import "@material/mwc-button/mwc-button"; import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state, query } from "lit/decorators";
import { formatDateNumeric } from "../../../common/datetime/format_date";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { isNavigationClick } from "../../../common/dom/is-navigation-click"; import { isNavigationClick } from "../../../common/dom/is-navigation-click";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../components/ha-md-dialog";
import "../../../components/ha-button";
import "../../../components/ha-dialog-header";
import "./dialog-repairs-issue-subtitle";
import "../../../components/ha-markdown"; import "../../../components/ha-markdown";
import { ignoreRepairsIssue, RepairsIssue } from "../../../data/repairs"; import { ignoreRepairsIssue, RepairsIssue } from "../../../data/repairs";
import { haStyleDialog } from "../../../resources/styles"; import { haStyleDialog } from "../../../resources/styles";
@ -20,12 +23,14 @@ class DialogRepairsIssue extends LitElement {
@state() private _params?: RepairsIssueDialogParams; @state() private _params?: RepairsIssueDialogParams;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
public showDialog(params: RepairsIssueDialogParams): void { public showDialog(params: RepairsIssueDialogParams): void {
this._params = params; this._params = params;
this._issue = this._params.issue; this._issue = this._params.issue;
} }
public closeDialog() { private _dialogClosed() {
if (this._params?.dialogClosedCallback) { if (this._params?.dialogClosedCallback) {
this._params.dialogClosedCallback(); this._params.dialogClosedCallback();
} }
@ -35,6 +40,10 @@ class DialogRepairsIssue extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
public closeDialog() {
this._dialog?.close();
}
protected render() { protected render() {
if (!this._issue) { if (!this._issue) {
return nothing; return nothing;
@ -43,23 +52,39 @@ class DialogRepairsIssue extends LitElement {
const learnMoreUrlIsHomeAssistant = const learnMoreUrlIsHomeAssistant =
this._issue.learn_more_url?.startsWith("homeassistant://") || false; this._issue.learn_more_url?.startsWith("homeassistant://") || false;
const dialogTitle =
this.hass.localize(
`component.${this._issue.domain}.issues.${this._issue.translation_key || this._issue.issue_id}.title`,
this._issue.translation_placeholders || {}
) || this.hass!.localize("ui.panel.config.repairs.dialog.title");
return html` return html`
<ha-dialog <ha-md-dialog
open open
@closed=${this.closeDialog} @closed=${this._dialogClosed}
scrimClickAction aria-labelledby="dialog-repairs-issue-title"
escapeKeyAction aria-describedby="dialog-repairs-issue-description"
.heading=${createCloseHeading(
this.hass,
this.hass.localize(
`component.${this._issue.domain}.issues.${
this._issue.translation_key || this._issue.issue_id
}.title`,
this._issue.translation_placeholders || {}
) || this.hass!.localize("ui.panel.config.repairs.dialog.title")
)}
> >
<div> <ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.generic.close") ?? "Close"}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
<span
slot="title"
id="dialog-repairs-issue-title"
.title=${dialogTitle}
>${dialogTitle}</span
>
<dialog-repairs-issue-subtitle
slot="subtitle"
.hass=${this.hass}
.issue=${this._issue}
></dialog-repairs-issue-subtitle>
</ha-dialog-header>
<div slot="content" class="dialog-content">
${this._issue.breaks_in_ha_version ${this._issue.breaks_in_ha_version
? html` ? html`
<ha-alert alert-type="warning"> <ha-alert alert-type="warning">
@ -71,6 +96,7 @@ class DialogRepairsIssue extends LitElement {
` `
: ""} : ""}
<ha-markdown <ha-markdown
id="dialog-repairs-issue-description"
allowsvg allowsvg
breaks breaks
@click=${this._clickHandler} @click=${this._clickHandler}
@ -92,51 +118,39 @@ class DialogRepairsIssue extends LitElement {
> >
` `
: ""} : ""}
<div class="secondary">
<span class=${this._issue.severity}
>${this.hass.localize(
`ui.panel.config.repairs.${this._issue.severity}`
)}
</span>
-
${this._issue.created
? formatDateNumeric(
new Date(this._issue.created),
this.hass.locale,
this.hass.config
)
: ""}
</div>
</div> </div>
${this._issue.learn_more_url <div slot="actions">
? html` <ha-button @click=${this._ignoreIssue}>
<a ${this._issue!.ignored
.href=${learnMoreUrlIsHomeAssistant ? this.hass!.localize("ui.panel.config.repairs.dialog.unignore")
? this._issue.learn_more_url.replace("homeassistant://", "/") : this.hass!.localize("ui.panel.config.repairs.dialog.ignore")}
: this._issue.learn_more_url} </ha-button>
.target=${learnMoreUrlIsHomeAssistant ? "" : "_blank"} ${this._issue.learn_more_url
@click=${learnMoreUrlIsHomeAssistant ? html`
? this.closeDialog <a
: undefined} rel="noopener noreferrer"
slot="primaryAction" .href=${learnMoreUrlIsHomeAssistant
rel="noopener noreferrer" ? this._issue.learn_more_url.replace(
> "homeassistant://",
<mwc-button "/"
.label=${this.hass!.localize( )
"ui.panel.config.repairs.dialog.learn" : this._issue.learn_more_url}
)} .target=${learnMoreUrlIsHomeAssistant ? "" : "_blank"}
></mwc-button> >
</a> <ha-button
` @click=${learnMoreUrlIsHomeAssistant
: ""} ? this.closeDialog
<mwc-button : undefined}
slot="secondaryAction" >
.label=${this._issue!.ignored ${this.hass!.localize(
? this.hass!.localize("ui.panel.config.repairs.dialog.unignore") "ui.panel.config.repairs.dialog.learn"
: this.hass!.localize("ui.panel.config.repairs.dialog.ignore")} )}
@click=${this._ignoreIssue} </ha-button>
></mwc-button> </a>
</ha-dialog> `
: ""}
</div>
</ha-md-dialog>
`; `;
} }
@ -154,28 +168,16 @@ class DialogRepairsIssue extends LitElement {
static styles: CSSResultGroup = [ static styles: CSSResultGroup = [
haStyleDialog, haStyleDialog,
css` css`
.dialog-content {
padding-top: 0;
}
ha-alert { ha-alert {
margin-bottom: 16px; margin-bottom: 16px;
display: block; display: block;
} }
a {
text-decoration: none;
}
.dismissed { .dismissed {
font-style: italic; font-style: italic;
} }
.secondary {
margin-top: 8px;
text-align: right;
color: var(--secondary-text-color);
}
.error,
.critical {
color: var(--error-color);
}
.warning {
color: var(--warning-color);
}
`, `,
]; ];
} }

View File

@ -1,12 +1,9 @@
import "@material/mwc-list/mwc-list";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter"; import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
import "../../../components/ha-alert"; import "../../../components/ha-md-list";
import "../../../components/ha-card"; import "../../../components/ha-md-list-item";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import { import {
fetchRepairsIssueData, fetchRepairsIssueData,
@ -48,19 +45,31 @@ class HaConfigRepairs extends LitElement {
count: this.total || this.repairsIssues.length, count: this.total || this.repairsIssues.length,
})} })}
</div> </div>
<mwc-list> <ha-md-list>
${issues.map( ${issues.map((issue) => {
(issue) => html` const domainName = domainToName(this.hass.localize, issue.domain);
<ha-list-item
twoline const createdBy =
graphic="medium" issue.created && domainName
? this.hass.localize("ui.panel.config.repairs.created_at_by", {
date: capitalizeFirstLetter(
relativeTime(new Date(issue.created), this.hass.locale)
),
integration: domainName,
})
: "";
return html`
<ha-md-list-item
.hasMeta=${!this.narrow} .hasMeta=${!this.narrow}
.issue=${issue} .issue=${issue}
class=${issue.ignored ? "ignored" : ""} class=${issue.ignored ? "ignored" : ""}
@click=${this._openShowMoreDialog} @click=${this._openShowMoreDialog}
type="button"
> >
<img <img
alt=${domainToName(this.hass.localize, issue.domain)} slot="start"
alt=${domainName}
loading="lazy" loading="lazy"
src=${brandsUrl({ src=${brandsUrl({
domain: issue.issue_domain || issue.domain, domain: issue.issue_domain || issue.domain,
@ -68,21 +77,18 @@ class HaConfigRepairs extends LitElement {
useFallback: true, useFallback: true,
darkOptimized: this.hass.themes?.darkMode, darkOptimized: this.hass.themes?.darkMode,
})} })}
.title=${domainToName(this.hass.localize, issue.domain)} .title=${domainName}
crossorigin="anonymous" crossorigin="anonymous"
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
slot="graphic"
/> />
<span <span slot="headline">
>${this.hass.localize( ${this.hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${issue.translation_key || issue.issue_id}.title`,
issue.translation_key || issue.issue_id
}.title`,
issue.translation_placeholders || {} issue.translation_placeholders || {}
) || ) ||
`${issue.domain}: ${issue.translation_key || issue.issue_id}`}</span `${issue.domain}: ${issue.translation_key || issue.issue_id}`}
> </span>
<span slot="secondary" class="secondary"> <span slot="supporting-text">
${issue.severity === "critical" || issue.severity === "error" ${issue.severity === "critical" || issue.severity === "error"
? html`<span class="error" ? html`<span class="error"
>${this.hass.localize( >${this.hass.localize(
@ -93,27 +99,25 @@ class HaConfigRepairs extends LitElement {
${(issue.severity === "critical" || ${(issue.severity === "critical" ||
issue.severity === "error") && issue.severity === "error") &&
issue.created issue.created
? " - " ? " ⸱ "
: ""}
${issue.created
? capitalizeFirstLetter(
relativeTime(new Date(issue.created), this.hass.locale)
)
: ""} : ""}
${createdBy
? html`<span .title=${createdBy}>${createdBy}</span>`
: nothing}
${issue.ignored ${issue.ignored
? ` - ${this.hass.localize( ? ` ${this.hass.localize(
"ui.panel.config.repairs.dialog.ignored_in_version_short", "ui.panel.config.repairs.dialog.ignored_in_version_short",
{ version: issue.dismissed_version } { version: issue.dismissed_version }
)}` )}`
: ""} : ""}
</span> </span>
${!this.narrow ${!this.narrow
? html`<ha-icon-next slot="meta"></ha-icon-next>` ? html`<ha-icon-next slot="end"></ha-icon-next>`
: ""} : ""}
</ha-list-item> </ha-md-list-item>
` `;
)} })}
</mwc-list> </ha-md-list>
`; `;
} }
@ -179,9 +183,6 @@ class HaConfigRepairs extends LitElement {
.ignored { .ignored {
opacity: var(--light-secondary-opacity); opacity: var(--light-secondary-opacity);
} }
ha-list-item {
--mdc-list-item-graphic-size: 40px;
}
button.show-more { button.show-more {
color: var(--primary-color); color: var(--primary-color);
text-align: left; text-align: left;
@ -198,9 +199,12 @@ class HaConfigRepairs extends LitElement {
outline: none; outline: none;
text-decoration: underline; text-decoration: underline;
} }
ha-list-item { ha-md-list-item img[slot="start"] {
cursor: pointer; width: 40px;
font-size: 16px; height: 40px;
}
ha-md-list-item span[slot="supporting-text"] {
white-space: nowrap;
} }
.error { .error {
color: var(--error-color); color: var(--error-color);

View File

@ -1,6 +1,7 @@
import { html } from "lit"; import { html, nothing } from "lit";
import { DataEntryFlowStep } from "../../../data/data_entry_flow"; import { DataEntryFlowStep } from "../../../data/data_entry_flow";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import "./dialog-repairs-issue-subtitle";
import { import {
RepairsIssue, RepairsIssue,
createRepairsFlow, createRepairsFlow,
@ -66,6 +67,16 @@ export const showRepairsFlowDialog = (
handleFlowStep: handleRepairsFlowStep, handleFlowStep: handleRepairsFlowStep,
deleteFlow: deleteRepairsFlow, deleteFlow: deleteRepairsFlow,
renderAbortHeader(hass) {
return html`
${hass.localize("ui.dialogs.repair_flow.form.header")}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
},
renderAbortDescription(hass, step) { renderAbortDescription(hass, step) {
const description = hass.localize( const description = hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${
@ -87,14 +98,18 @@ export const showRepairsFlowDialog = (
}, },
renderShowFormStepHeader(hass, step) { renderShowFormStepHeader(hass, step) {
return ( return html`
hass.localize( ${hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`, }.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step) mergePlaceholders(issue, step)
) || hass.localize("ui.dialogs.repair_flow.form.header") ) || hass.localize("ui.dialogs.repair_flow.form.header")}
); <dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
}, },
renderShowFormStepDescription(hass, step) { renderShowFormStepDescription(hass, step) {
@ -113,7 +128,7 @@ export const showRepairsFlowDialog = (
.content=${description} .content=${description}
></ha-markdown> ></ha-markdown>
` `
: ""}`; : nothing}`;
}, },
renderShowFormStepFieldLabel(hass, step, field, options) { renderShowFormStepFieldLabel(hass, step, field, options) {
@ -135,7 +150,7 @@ export const showRepairsFlowDialog = (
return html`${renderIssueDescription(hass, issue)} return html`${renderIssueDescription(hass, issue)}
${description ${description
? html`<ha-markdown breaks .content=${description}></ha-markdown>` ? html`<ha-markdown breaks .content=${description}></ha-markdown>`
: ""}`; : nothing}`;
}, },
renderShowFormStepFieldError(hass, step, error) { renderShowFormStepFieldError(hass, step, error) {
@ -181,14 +196,18 @@ export const showRepairsFlowDialog = (
}, },
renderShowFormProgressHeader(hass, step) { renderShowFormProgressHeader(hass, step) {
return ( return html`
hass.localize( ${hass.localize(
`component.${issue.domain}.issues.step.${ `component.${issue.domain}.issues.step.${
issue.translation_key || issue.issue_id issue.translation_key || issue.issue_id
}.fix_flow.${step.step_id}.title`, }.fix_flow.${step.step_id}.title`,
mergePlaceholders(issue, step) mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`) ) || hass.localize(`component.${issue.domain}.title`)}
); <dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
}, },
renderShowFormProgressDescription(hass, step) { renderShowFormProgressDescription(hass, step) {
@ -206,18 +225,22 @@ export const showRepairsFlowDialog = (
.content=${description} .content=${description}
></ha-markdown> ></ha-markdown>
` `
: ""}`; : nothing}`;
}, },
renderMenuHeader(hass, step) { renderMenuHeader(hass, step) {
return ( return html`
hass.localize( ${hass.localize(
`component.${issue.domain}.issues.${ `component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`, }.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step) mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`) ) || hass.localize(`component.${issue.domain}.title`)}
); <dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
}, },
renderMenuDescription(hass, step) { renderMenuDescription(hass, step) {
@ -236,7 +259,7 @@ export const showRepairsFlowDialog = (
.content=${description} .content=${description}
></ha-markdown> ></ha-markdown>
` `
: ""}`; : nothing}`;
}, },
renderMenuOption(hass, step, option) { renderMenuOption(hass, step, option) {

View File

@ -1972,6 +1972,8 @@
"system_information": "System information", "system_information": "System information",
"integration_startup_time": "Integration startup time", "integration_startup_time": "Integration startup time",
"copy": "Copy", "copy": "Copy",
"reported_by": "Reported by {integration}",
"created_at_by": "{date} by {integration}",
"dialog": { "dialog": {
"title": "Repair", "title": "Repair",
"fix": "Repair", "fix": "Repair",