Simplify localize (#2593)

* Simplify localize

* Delete unused file
This commit is contained in:
Paulus Schoutsen 2019-01-27 15:19:59 -08:00 committed by GitHub
parent 3e1c22edcd
commit 0a09eabce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 179 additions and 236 deletions

View File

@ -1,4 +1,4 @@
import { LocalizeFunc } from "../../mixins/localize-base-mixin";
import { LocalizeFunc } from "../translations/localize";
/**
* Calculate a string representing a date object as relative time from now.

View File

@ -3,7 +3,7 @@ import computeStateDomain from "./compute_state_domain";
import formatDateTime from "../datetime/format_date_time";
import formatDate from "../datetime/format_date";
import formatTime from "../datetime/format_time";
import { LocalizeFunc } from "../../mixins/localize-base-mixin";
import { LocalizeFunc } from "../translations/localize";
export default (
localize: LocalizeFunc,

View File

@ -0,0 +1,81 @@
import IntlMessageFormat from "intl-messageformat/src/main";
import { Resources } from "../../types";
export type LocalizeFunc = (key: string, ...args: any[]) => string;
interface FormatType {
[format: string]: any;
}
export interface FormatsType {
number: FormatType;
date: FormatType;
time: FormatType;
}
/**
* Adapted from Polymer app-localize-behavior.
*
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
* Optional dictionary of user defined formats, as explained here:
* http://formatjs.io/guides/message-syntax/#custom-formats
*
* For example, a valid dictionary of formats would be:
* this.formats = {
* number: { USD: { style: 'currency', currency: 'USD' } }
* }
*/
export const computeLocalize = (
cache: any,
language: string,
resources: Resources,
formats?: FormatsType
): LocalizeFunc => {
// Everytime any of the parameters change, invalidate the strings cache.
cache._localizationCache = {};
return (key, ...args) => {
if (!key || !resources || !language || !resources[language]) {
return "";
}
// Cache the key/value pairs for the same language, so that we don't
// do extra work if we're just reusing strings across an application.
const translatedValue = resources[language][key];
if (!translatedValue) {
return "";
}
const messageKey = key + translatedValue;
let translatedMessage = cache._localizationCache[messageKey];
if (!translatedMessage) {
translatedMessage = new (IntlMessageFormat as any)(
translatedValue,
language,
formats
);
cache._localizationCache[messageKey] = translatedMessage;
}
const argObject = {};
for (let i = 0; i < args.length; i += 2) {
argObject[args[i]] = args[i + 1];
}
try {
return translatedMessage.format(argObject);
} catch (err) {
return "Translation " + err;
}
};
};

View File

@ -15,7 +15,7 @@ import stateIcon from "../../common/entity/state_icon";
import timerTimeRemaining from "../../common/entity/timer_time_remaining";
import secondsToDuration from "../../common/datetime/seconds_to_duration";
import { fireEvent } from "../../common/dom/fire_event";
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../types";
import "../ha-label-badge";
@ -23,7 +23,8 @@ import "../ha-label-badge";
* @appliesMixin LocalizeMixin
* @appliesMixin EventsMixin
*/
export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
export class HaStateLabelBadge extends LitElement {
public hass?: HomeAssistant;
public state?: HassEntity;
private _connected?: boolean;
private _updateRemaining?: number;
@ -108,7 +109,7 @@ export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
default:
return state.state === "unknown"
? "-"
: this.localize(`component.${domain}.state.${state.state}`) ||
: this.hass!.localize(`component.${domain}.state.${state.state}`) ||
state.state;
}
}
@ -163,8 +164,8 @@ export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
// the state translations that are truncated to fit within the badge label. Translations
// are only added for device_tracker and alarm_control_panel.
return (
this.localize(`state_badge.${domain}.${state.state}`) ||
this.localize(`state_badge.default.${state.state}`) ||
this.hass!.localize(`state_badge.${domain}.${state.state}`) ||
this.hass!.localize(`state_badge.default.${state.state}`) ||
state.state
);
}

View File

@ -6,8 +6,8 @@ import {
LineChartUnit,
} from "./history";
import { HomeAssistant } from "../types";
import { LocalizeFunc } from "../mixins/localize-base-mixin";
import { HassEntity } from "home-assistant-js-websocket";
import { LocalizeFunc } from "../common/translations/localize";
interface CacheConfig {
refresh: number;

View File

@ -2,8 +2,8 @@ import computeStateName from "../common/entity/compute_state_name";
import computeStateDomain from "../common/entity/compute_state_domain";
import computeStateDisplay from "../common/entity/compute_state_display";
import { HassEntity } from "home-assistant-js-websocket";
import { LocalizeFunc } from "../mixins/localize-base-mixin";
import { HomeAssistant } from "../types";
import { LocalizeFunc } from "../common/translations/localize";
const DOMAINS_USE_LAST_UPDATED = ["climate", "water_heater"];
const LINE_ATTRIBUTES_TO_KEEP = [

View File

@ -136,6 +136,7 @@ export const provideHass = (
language: getActiveTranslation(),
resources: null as any,
localize: () => "",
translationMetadata: translationMetadata as any,
dockedSidebar: false,

View File

@ -53,6 +53,7 @@ export default (superClass) =>
language: getActiveTranslation(),
// If resources are already loaded, don't discard them
resources: (this.hass && this.hass.resources) || null,
localize: () => "",
translationMetadata: translationMetadata,
dockedSidebar: false,

View File

@ -25,13 +25,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
if (!this._discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
// Temp. Somehow the localize func is not getting recalculated for
// this class. Manually generating one. Will be fixed when we move
// the localize function to the hass object.
const { language, resources } = this.hass!;
el.text = (this as any).__computeLocalize(language, resources)(
"ui.notification_toast.connection_lost"
);
el.text = this.hass!.localize("ui.notification_toast.connection_lost");
this._discToast = el;
this.shadowRoot!.appendChild(el as any);
}

View File

@ -1,14 +1,17 @@
import { translationMetadata } from "../../resources/translations-metadata";
import { getTranslation } from "../../util/hass-translation";
import { storeState } from "../../util/ha-pref-storage";
import { Constructor, LitElement } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { computeLocalize } from "../../common/translations/localize";
/*
* superClass needs to contain `this.hass` and `this._updateHass`.
*/
export default (superClass) =>
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
class extends superClass {
firstUpdated(changedProps) {
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-language-select", (e) =>
this._selectLanguage(e)
@ -16,71 +19,78 @@ export default (superClass) =>
this._loadResources();
}
hassConnected() {
protected hassConnected() {
super.hassConnected();
this._loadBackendTranslations();
}
hassReconnected() {
protected hassReconnected() {
super.hassReconnected();
this._loadBackendTranslations();
}
panelUrlChanged(newPanelUrl) {
protected panelUrlChanged(newPanelUrl) {
super.panelUrlChanged(newPanelUrl);
this._loadTranslationFragment(newPanelUrl);
}
async _loadBackendTranslations() {
if (!this.hass.language) return;
private async _loadBackendTranslations() {
const hass = this.hass;
if (!hass || !hass.language) {
return;
}
const language = this.hass.selectedLanguage || this.hass.language;
const language = hass.selectedLanguage || hass.language;
const { resources } = await this.hass.callWS({
const { resources } = await hass.callWS({
type: "frontend/get_translations",
language,
});
// If we've switched selected languages just ignore this response
if ((this.hass.selectedLanguage || this.hass.language) !== language)
if ((hass.selectedLanguage || hass.language) !== language) {
return;
}
this._updateResources(language, resources);
}
_loadTranslationFragment(panelUrl) {
private _loadTranslationFragment(panelUrl) {
if (translationMetadata.fragments.includes(panelUrl)) {
this._loadResources(panelUrl);
}
}
async _loadResources(fragment) {
private async _loadResources(fragment?) {
const result = await getTranslation(fragment);
this._updateResources(result.language, result.data);
}
_updateResources(language, data) {
private _updateResources(language, data) {
// Update the language in hass, and update the resources with the newly
// loaded resources. This merges the new data on top of the old data for
// this language, so that the full translation set can be loaded across
// multiple fragments.
this._updateHass({
language: language,
resources: {
[language]: Object.assign(
{},
this.hass && this.hass.resources && this.hass.resources[language],
data
),
const resources = {
[language]: {
...(this.hass &&
this.hass.resources &&
this.hass.resources[language]),
...data,
},
};
this._updateHass({
language,
resources,
localize: computeLocalize(this, language, resources),
});
}
_selectLanguage(event) {
private _selectLanguage(event) {
this._updateHass({ selectedLanguage: event.detail.language });
storeState(this.hass);
this._loadResources();
this._loadBackendTranslations();
this._loadTranslationFragment(this.hass.panelUrl);
this._loadTranslationFragment(this.hass!.panelUrl);
}
};

View File

@ -4,10 +4,10 @@ import {
PropertyDeclarations,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../types";
import { getActiveTranslation } from "../util/hass-translation";
import { LocalizeFunc, LocalizeMixin } from "./localize-base-mixin";
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
import { computeLocalize } from "../common/translations/localize";
import { LocalizeMixin } from "../types";
const empty = () => "";
@ -22,12 +22,8 @@ export const litLocalizeLiteMixin = <T extends LitElement>(
): Constructor<T & LocalizeMixin & LitLocalizeLiteMixin> =>
// @ts-ignore
class extends localizeLiteBaseMixin(superClass) {
protected hass?: HomeAssistant;
protected localize!: LocalizeFunc;
static get properties(): PropertyDeclarations {
return {
hass: {},
localize: {},
language: {},
resources: {},
@ -45,7 +41,11 @@ export const litLocalizeLiteMixin = <T extends LitElement>(
public connectedCallback(): void {
super.connectedCallback();
this._initializeLocalizeLite();
this.localize = this.__computeLocalize(this.language, this.resources);
this.localize = computeLocalize(
this.constructor.prototype,
this.language,
this.resources
);
}
public updated(changedProperties: PropertyValues) {
@ -54,7 +54,11 @@ export const litLocalizeLiteMixin = <T extends LitElement>(
changedProperties.has("language") ||
changedProperties.has("resources")
) {
this.localize = this.__computeLocalize(this.language, this.resources);
this.localize = computeLocalize(
this.constructor.prototype,
this.language,
this.resources
);
}
}
};

View File

@ -1,15 +1,5 @@
import {
Constructor,
LitElement,
PropertyDeclarations,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../types";
import {
localizeBaseMixin,
LocalizeFunc,
LocalizeMixin,
} from "./localize-base-mixin";
import { Constructor, LitElement } from "lit-element";
import { HomeAssistant, LocalizeMixin } from "../types";
const empty = () => "";
@ -17,61 +7,10 @@ export const hassLocalizeLitMixin = <T extends LitElement>(
superClass: Constructor<T>
): Constructor<T & LocalizeMixin> =>
// @ts-ignore
class extends localizeBaseMixin(superClass) {
protected hass?: HomeAssistant;
protected localize!: LocalizeFunc;
class extends superClass {
public hass?: HomeAssistant;
static get properties(): PropertyDeclarations {
return {
hass: {},
localize: {},
};
}
constructor() {
super();
// This will prevent undefined errors if called before connected to DOM.
this.localize = empty;
}
public connectedCallback(): void {
super.connectedCallback();
if (this.localize === empty) {
let language;
let resources;
if (this.hass) {
language = this.hass.language;
resources = this.hass.resources;
}
this.localize = this.__computeLocalize(language, resources);
}
}
public updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (!changedProperties.has("hass")) {
return;
}
let oldLanguage;
let oldResources;
const hass = changedProperties.get("hass") as HomeAssistant;
if (hass) {
oldLanguage = hass.language;
oldResources = hass.resources;
}
let language;
let resources;
if (this.hass) {
language = this.hass.language;
resources = this.hass.resources;
}
if (oldLanguage !== language || oldResources !== resources) {
this.localize = this.__computeLocalize(language, resources);
}
get localize() {
return this.hass ? this.hass.localize : empty;
}
};

View File

@ -1,114 +0,0 @@
import IntlMessageFormat from "intl-messageformat/src/main";
import { HomeAssistant } from "../types";
/**
* Adapted from Polymer app-localize-behavior.
*
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
* Optional dictionary of user defined formats, as explained here:
* http://formatjs.io/guides/message-syntax/#custom-formats
*
* For example, a valid dictionary of formats would be:
* this.formats = {
* number: { USD: { style: 'currency', currency: 'USD' } }
* }
*/
interface FormatType {
[format: string]: any;
}
export interface FormatsType {
number: FormatType;
date: FormatType;
time: FormatType;
}
export type LocalizeFunc = (key: string, ...args: any[]) => string;
export interface LocalizeMixin {
hass?: HomeAssistant;
localize: LocalizeFunc;
}
export const localizeBaseMixin = (superClass) =>
class extends superClass {
/**
* Returns a computed `localize` method, based on the current `language`.
*/
public __computeLocalize(
language: string,
resources: string,
formats?: FormatsType
): LocalizeFunc {
const proto = this.constructor.prototype;
// Check if localCache exist just in case.
this.__checkLocalizationCache(proto);
// Everytime any of the parameters change, invalidate the strings cache.
if (!proto.__localizationCache) {
proto.__localizationCache = {
messages: {},
};
}
proto.__localizationCache.messages = {};
return (key, ...args) => {
if (!key || !resources || !language || !resources[language]) {
return "";
}
// Cache the key/value pairs for the same language, so that we don't
// do extra work if we're just reusing strings across an application.
const translatedValue = resources[language][key];
if (!translatedValue) {
return this.useKeyIfMissing ? key : "";
}
const messageKey = key + translatedValue;
let translatedMessage = proto.__localizationCache.messages[messageKey];
if (!translatedMessage) {
translatedMessage = new (IntlMessageFormat as any)(
translatedValue,
language,
formats
);
proto.__localizationCache.messages[messageKey] = translatedMessage;
}
const argObject = {};
for (let i = 0; i < args.length; i += 2) {
argObject[args[i]] = args[i + 1];
}
try {
return translatedMessage.format(argObject);
} catch (err) {
return "Translation " + err;
}
};
}
public __checkLocalizationCache(proto) {
// do nothing if proto is undefined.
if (proto === undefined) {
return;
}
// In the event proto not have __localizationCache object, create it.
if (proto.__localizationCache === undefined) {
proto.__localizationCache = {
messages: {},
};
}
}
};

View File

@ -1,14 +1,13 @@
/**
* Lite base mixin to add localization without depending on the Hass object.
*/
import { localizeBaseMixin } from "./localize-base-mixin";
import { getTranslation } from "../util/hass-translation";
/**
* @polymerMixin
*/
export const localizeLiteBaseMixin = (superClass) =>
class extends localizeBaseMixin(superClass) {
class extends superClass {
protected _initializeLocalizeLite() {
if (this.resources) {
return;

View File

@ -4,6 +4,7 @@
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
import { getActiveTranslation } from "../util/hass-translation";
import { localizeLiteBaseMixin } from "./localize-lite-base-mixin";
import { computeLocalize } from "../common/translations/localize";
/**
* @polymerMixin
@ -36,5 +37,14 @@ export const localizeLiteMixin = dedupingMixin(
super.ready();
this._initializeLocalizeLite();
}
protected __computeLocalize(language, resources, formats?) {
return computeLocalize(
this.constructor.prototype,
language,
resources,
formats
);
}
}
);

View File

@ -1,5 +1,4 @@
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
import { localizeBaseMixin } from "./localize-base-mixin";
/**
* Polymer Mixin to enable a localize function powered by language/resources from hass object.
*
@ -7,7 +6,7 @@ import { localizeBaseMixin } from "./localize-base-mixin";
*/
export default dedupingMixin(
(superClass) =>
class extends localizeBaseMixin(superClass) {
class extends superClass {
static get properties() {
return {
hass: Object,
@ -19,10 +18,13 @@ export default dedupingMixin(
*/
localize: {
type: Function,
computed:
"__computeLocalize(hass.language, hass.resources, formats)",
computed: "__computeLocalize(hass.localize)",
},
};
}
__computeLocalize(localize) {
return localize;
}
}
);

View File

@ -11,10 +11,10 @@ import computeStateName from "../../../common/entity/compute_state_name";
import splitByGroups from "../../../common/entity/split_by_groups";
import computeObjectId from "../../../common/entity/compute_object_id";
import computeStateDomain from "../../../common/entity/compute_state_domain";
import { LocalizeFunc } from "../../../mixins/localize-base-mixin";
import computeDomain from "../../../common/entity/compute_domain";
import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types";
import { EntitiesCardConfig } from "../cards/hui-entities-card";
import { LocalizeFunc } from "../../../common/translations/localize";
const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
const DOMAINS_BADGES = [

View File

@ -8,6 +8,7 @@ import {
HassEntityAttributeBase,
HassServices,
} from "home-assistant-js-websocket";
import { LocalizeFunc } from "./common/translations/localize";
declare global {
var __DEV__: boolean;
@ -92,6 +93,10 @@ export interface Notification {
created_at: string;
}
export interface Resources {
[language: string]: { [key: string]: string };
}
export interface HomeAssistant {
auth: Auth;
connection: Connection;
@ -103,14 +108,19 @@ export interface HomeAssistant {
selectedTheme?: string | null;
panels: Panels;
panelUrl: string;
// i18n
language: string;
resources: { [key: string]: any };
selectedLanguage?: string;
resources: Resources;
localize: LocalizeFunc;
translationMetadata: {
fragments: string[];
translations: {
[lang: string]: Translation;
};
};
dockedSidebar: boolean;
moreInfoEntityId: string;
user: User;
@ -196,3 +206,8 @@ export interface PanelElement extends HTMLElement {
route?: Route | null;
panel?: Panel;
}
export interface LocalizeMixin {
hass?: HomeAssistant;
localize: LocalizeFunc;
}