ha-nunjucks 1.7.3 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/filters.js CHANGED
@@ -45,6 +45,9 @@ const HASS_FILTERS = {
45
45
  state_translated,
46
46
  attr_name_translated,
47
47
  attr_value_translated,
48
+ number_translated,
49
+ date_translated,
50
+ datetime_translated,
48
51
  // Groups
49
52
  expand,
50
53
  // Devices
@@ -92,9 +95,7 @@ const FILTERS = {
92
95
  timestamp_local,
93
96
  timestamp_utc,
94
97
  timestamp_custom,
95
- date_translated,
96
98
  time_translated,
97
- datetime_translated,
98
99
  // To/From JSON
99
100
  to_json,
100
101
  from_json,
@@ -136,7 +137,6 @@ const FILTERS = {
136
137
  ord,
137
138
  multiply,
138
139
  add,
139
- number_translated,
140
140
  // Type conversions
141
141
  str,
142
142
  // Functions and Filters to Process Raw Data
package/dist/globals.js CHANGED
@@ -58,6 +58,9 @@ const HASS_GLOBALS = {
58
58
  state_translated,
59
59
  attr_name_translated,
60
60
  attr_value_translated,
61
+ number_translated,
62
+ date_translated,
63
+ datetime_translated,
61
64
  // Groups
62
65
  expand,
63
66
  // Entities
@@ -116,9 +119,7 @@ const GLOBALS = {
116
119
  time_since,
117
120
  time_until,
118
121
  as_timedelta,
119
- date_translated,
120
122
  time_translated,
121
- datetime_translated,
122
123
  // Version
123
124
  version,
124
125
  // Numeric,
@@ -144,7 +145,6 @@ const GLOBALS = {
144
145
  mod,
145
146
  wrap,
146
147
  remap,
147
- number_translated,
148
148
  // Type Conversions
149
149
  set,
150
150
  list,
package/dist/index.js CHANGED
@@ -4,10 +4,11 @@ import { addFilters } from './filters';
4
4
  import { addGlobals } from './globals';
5
5
  import { handleWhenReady } from './helpers';
6
6
  import { addTests } from './tests';
7
- import { fetchConfigEntries } from './utils/config_entry';
7
+ import { subscribeConfigEntries } from './utils/config_entry';
8
8
  import { fetchEntityRegistry } from './utils/entities';
9
9
  import { fetchRepairsIssues } from './utils/issues';
10
10
  import { fetchLabelRegistry } from './utils/labels';
11
+ import { getNumberFormatter, getTimeFormatter } from './utils/state_translated';
11
12
  import { buildStatesObject } from './utils/states';
12
13
  import { version } from './utils/version';
13
14
  window.haNunjucks ||= {};
@@ -29,33 +30,30 @@ if (version(packageInfo.version).compare(window.haNunjucks.version || '0.0.0') >
29
30
  ...window.haNunjucks,
30
31
  hass: ha.hass,
31
32
  labelRegistry: {
32
- event: 'label_registry_updated',
33
+ updateEvent: 'label_registry_updated',
33
34
  fetchRegistry: fetchLabelRegistry,
34
35
  labelId: {},
35
36
  name2LabelId: {},
36
37
  },
37
38
  entityRegistry: {
38
- event: 'entity_registry_updated',
39
+ updateEvent: 'entity_registry_updated',
39
40
  fetchRegistry: fetchEntityRegistry,
40
41
  entityId2ConfigEntryId: {},
41
42
  configEntryId2EntityIds: {},
42
43
  },
43
- configEntries: {
44
- event: 'config_entries/subscribe',
45
- fetchRegistry: fetchConfigEntries,
46
- entryId: {},
47
- title2EntryId: {},
48
- },
49
44
  repairsIssues: {
50
- event: 'repairs/list_issues',
45
+ updateEvent: 'repairs_issue_registry_updated',
51
46
  fetchRegistry: fetchRepairsIssues,
52
47
  issues: {},
53
48
  },
49
+ configEntries: {
50
+ entryId: {},
51
+ title2EntryId: {},
52
+ },
54
53
  };
55
54
  const registries = [
56
55
  'labelRegistry',
57
56
  'entityRegistry',
58
- 'configEntries',
59
57
  'repairsIssues',
60
58
  ];
61
59
  for (const registry of registries) {
@@ -65,21 +63,20 @@ if (version(packageInfo.version).compare(window.haNunjucks.version || '0.0.0') >
65
63
  window.haNunjucks[registry].timeout = setTimeout(() => {
66
64
  window.haNunjucks[registry].fetchRegistry(ha.hass);
67
65
  }, 500);
68
- }, window.haNunjucks[registry].event);
66
+ }, window.haNunjucks[registry].updateEvent);
69
67
  }
68
+ subscribeConfigEntries(ha.hass);
70
69
  // States object
71
70
  buildStatesObject();
72
71
  // Number and datetime translators
73
- window.haNunjucks.numberFormat = new Intl.NumberFormat(ha.hass.language);
72
+ window.haNunjucks.numberFormat = getNumberFormatter(ha.hass);
74
73
  window.haNunjucks.dateFormat = new Intl.DateTimeFormat(ha.hass.language, {
75
- dateStyle: 'full',
76
- });
77
- window.haNunjucks.timeFormat = new Intl.DateTimeFormat(ha.hass.language, {
78
- timeStyle: 'long',
74
+ dateStyle: 'long',
79
75
  });
80
- window.haNunjucks.datetimeFormat = new Intl.DateTimeFormat(ha.hass.language, { dateStyle: 'full', timeStyle: 'long' });
76
+ window.haNunjucks.timeFormat = getTimeFormatter(ha.hass);
81
77
  window.haNunjucks.ordinalFormat = new Intl.PluralRules('en-US', // ha.hass.language, // Use english for proper numeric suffixes
82
78
  { type: 'ordinal' });
79
+ console.info(`%c HA-NUNJUCKS v${packageInfo.version}`, 'color: white; font-weight: bold; background: darkgreen');
83
80
  }, () => {
84
81
  const ha = document.querySelector('home-assistant');
85
82
  return ha?.hass?.connected && ha?.hass?.connection?.connected;
@@ -22,6 +22,7 @@ export interface HomeAssistant {
22
22
  panelUrl: string;
23
23
  language: string;
24
24
  selectedLanguage: string | null;
25
+ locale: FrontendLocaleData;
25
26
  suspendWhenHidden: boolean;
26
27
  enableShortcuts: boolean;
27
28
  vibrate: boolean;
@@ -40,3 +41,48 @@ export interface HomeAssistant {
40
41
  export interface HassElement extends HTMLElement {
41
42
  hass: HomeAssistant;
42
43
  }
44
+ interface FrontendLocaleData {
45
+ language: string;
46
+ number_format: NumberFormat;
47
+ time_format: TimeFormat;
48
+ date_format: DateFormat;
49
+ first_weekday: FirstWeekday;
50
+ time_zone: TimeZone;
51
+ }
52
+ export declare enum NumberFormat {
53
+ language = "language",
54
+ system = "system",
55
+ comma_decimal = "comma_decimal",
56
+ decimal_comma = "decimal_comma",
57
+ quote_decimal = "quote_decimal",
58
+ space_comma = "space_comma",
59
+ none = "none"
60
+ }
61
+ export declare enum TimeFormat {
62
+ language = "language",
63
+ system = "system",
64
+ am_pm = "12",
65
+ twenty_four = "24"
66
+ }
67
+ export declare enum TimeZone {
68
+ local = "local",
69
+ server = "server"
70
+ }
71
+ export declare enum DateFormat {
72
+ language = "language",
73
+ system = "system",
74
+ DMY = "DMY",
75
+ MDY = "MDY",
76
+ YMD = "YMD"
77
+ }
78
+ export declare enum FirstWeekday {
79
+ language = "language",
80
+ monday = "monday",
81
+ tuesday = "tuesday",
82
+ wednesday = "wednesday",
83
+ thursday = "thursday",
84
+ friday = "friday",
85
+ saturday = "saturday",
86
+ sunday = "sunday"
87
+ }
88
+ export {};
@@ -1 +1,41 @@
1
- export {};
1
+ export var NumberFormat;
2
+ (function (NumberFormat) {
3
+ NumberFormat["language"] = "language";
4
+ NumberFormat["system"] = "system";
5
+ NumberFormat["comma_decimal"] = "comma_decimal";
6
+ NumberFormat["decimal_comma"] = "decimal_comma";
7
+ NumberFormat["quote_decimal"] = "quote_decimal";
8
+ NumberFormat["space_comma"] = "space_comma";
9
+ NumberFormat["none"] = "none";
10
+ })(NumberFormat || (NumberFormat = {}));
11
+ export var TimeFormat;
12
+ (function (TimeFormat) {
13
+ TimeFormat["language"] = "language";
14
+ TimeFormat["system"] = "system";
15
+ TimeFormat["am_pm"] = "12";
16
+ TimeFormat["twenty_four"] = "24";
17
+ })(TimeFormat || (TimeFormat = {}));
18
+ export var TimeZone;
19
+ (function (TimeZone) {
20
+ TimeZone["local"] = "local";
21
+ TimeZone["server"] = "server";
22
+ })(TimeZone || (TimeZone = {}));
23
+ export var DateFormat;
24
+ (function (DateFormat) {
25
+ DateFormat["language"] = "language";
26
+ DateFormat["system"] = "system";
27
+ DateFormat["DMY"] = "DMY";
28
+ DateFormat["MDY"] = "MDY";
29
+ DateFormat["YMD"] = "YMD";
30
+ })(DateFormat || (DateFormat = {}));
31
+ export var FirstWeekday;
32
+ (function (FirstWeekday) {
33
+ FirstWeekday["language"] = "language";
34
+ FirstWeekday["monday"] = "monday";
35
+ FirstWeekday["tuesday"] = "tuesday";
36
+ FirstWeekday["wednesday"] = "wednesday";
37
+ FirstWeekday["thursday"] = "thursday";
38
+ FirstWeekday["friday"] = "friday";
39
+ FirstWeekday["saturday"] = "saturday";
40
+ FirstWeekday["sunday"] = "sunday";
41
+ })(FirstWeekday || (FirstWeekday = {}));
@@ -153,6 +153,10 @@ export interface ConfigEntry {
153
153
  error_reason_translation_key?: string;
154
154
  error_reason_translation_placeholders?: Record<string, string>;
155
155
  }
156
+ export interface ConfigEntryUpdate {
157
+ type: null | 'added' | 'removed' | 'updated';
158
+ entry: ConfigEntry;
159
+ }
156
160
  export interface RepairsIssue {
157
161
  domain: string;
158
162
  issue_domain?: string;
@@ -1,5 +1,5 @@
1
1
  import { HomeAssistant } from '../models/interfaces/hass';
2
- export declare function fetchConfigEntries(hass: HomeAssistant): Promise<void>;
2
+ export declare function subscribeConfigEntries(hass: HomeAssistant): Promise<void>;
3
3
  export declare function config_entry_id(entity_id: string): string;
4
4
  declare const ConfigEntryAttributes: readonly ["domain", "title", "state", "source", "disabled_by"];
5
5
  type ConfigEntryAttribute = (typeof ConfigEntryAttributes)[number];
@@ -1,19 +1,37 @@
1
- export async function fetchConfigEntries(hass) {
2
- const entries = await hass.callWS({
3
- type: 'config_entries/get',
1
+ export async function subscribeConfigEntries(hass) {
2
+ await hass.connection.subscribeMessage((updates) => {
3
+ const entryId = window.haNunjucks.configEntries.entryId || {};
4
+ const title2EntryId = window.haNunjucks.configEntries.title2EntryId || {};
5
+ for (const update of updates) {
6
+ if (update.type == 'removed') {
7
+ delete entryId[update.entry.entry_id];
8
+ if (title2EntryId[update.entry.title]) {
9
+ title2EntryId[update.entry.title] = title2EntryId[update.entry.title].filter((id) => id != update.entry.entry_id);
10
+ if (!title2EntryId[update.entry.title].length) {
11
+ delete title2EntryId[update.entry.title];
12
+ }
13
+ }
14
+ }
15
+ else {
16
+ if (entryId[update.entry.entry_id] &&
17
+ entryId[update.entry.entry_id].title != update.entry.title) {
18
+ delete title2EntryId[entryId[update.entry.entry_id].title];
19
+ }
20
+ entryId[update.entry.entry_id] = update.entry;
21
+ title2EntryId[update.entry.title] ??= [];
22
+ if (!title2EntryId[update.entry.title].includes(update.entry.entry_id)) {
23
+ title2EntryId[update.entry.title].push(update.entry.entry_id);
24
+ }
25
+ }
26
+ }
27
+ window.haNunjucks.configEntries = {
28
+ ...window.haNunjucks.configEntries,
29
+ entryId,
30
+ title2EntryId,
31
+ };
32
+ }, {
33
+ type: 'config_entries/subscribe',
4
34
  });
5
- const entryId = {};
6
- const title2EntryId = {};
7
- for (const entry of entries) {
8
- entryId[entry.entry_id] = entry;
9
- title2EntryId[entry.title] ??= [];
10
- title2EntryId[entry.title].push(entry.entry_id);
11
- }
12
- window.haNunjucks.configEntries = {
13
- ...window.haNunjucks.configEntries,
14
- entryId,
15
- title2EntryId,
16
- };
17
35
  }
18
36
  export function config_entry_id(entity_id) {
19
37
  return window.haNunjucks.entityRegistry.entityId2ConfigEntryId[entity_id];
@@ -4,7 +4,9 @@ export async function fetchRepairsIssues(hass) {
4
4
  })).issues;
5
5
  const issues = {};
6
6
  for (const issue of repairsIssues) {
7
- issues[`${issue.issue_domain || issue.domain},${issue.issue_id}`] = issue;
7
+ if (!issue.ignored) {
8
+ issues[`${issue.issue_domain || issue.domain},${issue.issue_id}`] = issue;
9
+ }
8
10
  }
9
11
  window.haNunjucks.repairsIssues.issues = issues;
10
12
  }
@@ -3,7 +3,9 @@ import { HomeAssistant } from '../models/interfaces/hass';
3
3
  export declare function state_translated(hass: HomeAssistant, entity_id: string, state?: string): string;
4
4
  export declare function attr_name_translated(hass: HomeAssistant, entity_id: string, attr_name: string): string;
5
5
  export declare function attr_value_translated(hass: HomeAssistant, entity_id: string, attr_name: string, attr_value?: string): any;
6
- export declare function number_translated(value: number, precision?: number): string | number;
7
- export declare function date_translated(value: date | datetime): string | date | datetime;
8
- export declare function time_translated(value: time | datetime): string | datetime | time;
9
- export declare function datetime_translated(value: datetime): string | datetime;
6
+ export declare function getNumberFormatter(hass: HomeAssistant): Intl.NumberFormat;
7
+ export declare function number_translated(hass: HomeAssistant, value: number, precision?: number): string | number;
8
+ export declare function date_translated(hass: HomeAssistant, value: date | datetime | Date | string): string | date | datetime | Date;
9
+ export declare function getTimeFormatter(hass: HomeAssistant): Intl.DateTimeFormat;
10
+ export declare function time_translated(value: time | datetime | Date | string): string | datetime | Date | time;
11
+ export declare function datetime_translated(hass: HomeAssistant, value: datetime | Date | string): string | datetime | Date;
@@ -1,3 +1,4 @@
1
+ import { DateFormat, NumberFormat, TimeFormat, TimeZone, } from '../models/interfaces/hass';
1
2
  export function state_translated(hass, entity_id, state) {
2
3
  try {
3
4
  return hass.formatEntityState(hass.states[entity_id], state);
@@ -11,9 +12,7 @@ export function attr_name_translated(hass, entity_id, attr_name) {
11
12
  return hass.formatEntityAttributeName(hass.states[entity_id], attr_name);
12
13
  }
13
14
  catch {
14
- return (attr_name ??
15
- hass.states[entity_id]?.attributes?.[attr_name] ??
16
- undefined);
15
+ return (attr_name ?? hass.states[entity_id]?.attributes?.[attr_name] ?? undefined);
17
16
  }
18
17
  }
19
18
  export function attr_value_translated(hass, entity_id, attr_name, attr_value) {
@@ -21,43 +20,151 @@ export function attr_value_translated(hass, entity_id, attr_name, attr_value) {
21
20
  return hass.formatEntityAttributeValue(hass.states[entity_id], attr_name, attr_value);
22
21
  }
23
22
  catch {
24
- return (attr_value ??
25
- hass.states[entity_id]?.attributes?.[attr_name] ??
26
- undefined);
23
+ return (attr_value ?? hass.states[entity_id]?.attributes?.[attr_name] ?? undefined);
27
24
  }
28
25
  }
29
- export function number_translated(value, precision) {
26
+ export function getNumberFormatter(hass) {
27
+ // https://github.com/home-assistant/frontend/blob/52ac052baf139e94b7ed6891eb0beace7e2f47d3/src/common/number/format_number.ts#L24
28
+ let language;
29
+ switch (hass.locale.number_format) {
30
+ case NumberFormat.comma_decimal:
31
+ language = ['en-US', 'en']; // Use United States with fallback to English formatting 1,234,567.89
32
+ break;
33
+ case NumberFormat.decimal_comma:
34
+ language = ['de', 'es', 'it']; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
35
+ break;
36
+ case NumberFormat.space_comma:
37
+ language = ['fr', 'sv', 'cs']; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
38
+ break;
39
+ case NumberFormat.quote_decimal:
40
+ language = ['de-CH']; // Use German (Switzerland) formatting 1'234'567.89
41
+ break;
42
+ default:
43
+ language = hass.locale.language ?? hass.language;
44
+ break;
45
+ }
46
+ return new Intl.NumberFormat(language);
47
+ }
48
+ export function number_translated(hass, value, precision) {
30
49
  value = Number(value);
31
50
  if (isNaN(value)) {
32
51
  return value;
33
52
  }
34
53
  if (precision) {
35
- return value.toLocaleString(window.haNunjucks.hass.language, {
54
+ return value.toLocaleString(hass.locale.language ?? hass.language, {
36
55
  minimumFractionDigits: precision,
37
56
  maximumFractionDigits: precision,
38
57
  });
39
58
  }
40
59
  return window.haNunjucks.numberFormat.format(value);
41
60
  }
42
- export function date_translated(value) {
61
+ export function date_translated(hass, value) {
62
+ // https://github.com/home-assistant/frontend/blob/52ac052baf139e94b7ed6891eb0beace7e2f47d3/src/common/datetime/format_date.ts#L59
43
63
  try {
44
- return window.haNunjucks.dateFormat.format(value.jsDate);
64
+ let date;
65
+ if (typeof value === 'string') {
66
+ date = new Date(`${value}T00:00:00`);
67
+ }
68
+ else if (value instanceof Date) {
69
+ date = value;
70
+ }
71
+ else {
72
+ date = value.jsDate;
73
+ }
74
+ let order;
75
+ switch (hass.locale.date_format) {
76
+ case DateFormat.DMY:
77
+ order = ['day', 'month', 'year'];
78
+ break;
79
+ case DateFormat.MDY:
80
+ order = ['month', 'day', 'year'];
81
+ break;
82
+ case DateFormat.YMD:
83
+ order = ['year', 'month', 'day'];
84
+ break;
85
+ default:
86
+ return window.haNunjucks.dateFormat.format(date);
87
+ }
88
+ const parts = window.haNunjucks.dateFormat.formatToParts(date);
89
+ const partsObj = {
90
+ literal: parts.find((value) => value.type === 'literal')?.value,
91
+ day: parts.find((value) => value.type === 'day')?.value,
92
+ month: parts.find((value) => value.type === 'month')?.value,
93
+ year: parts.find((value) => value.type === 'year')?.value,
94
+ };
95
+ const lastPart = parts[parts.length - 1];
96
+ partsObj.lastLiteral = lastPart?.type === 'literal' ? lastPart?.value : '';
97
+ if (hass.locale.language === 'bg' &&
98
+ hass.locale.date_format === DateFormat.YMD) {
99
+ partsObj.lastLiteral = '';
100
+ }
101
+ return `${partsObj[order[0]]}${partsObj.literal}${partsObj[order[1]]}${partsObj.literal}${partsObj[order[2]]}${partsObj.lastLiteral}`;
45
102
  }
46
103
  catch {
47
104
  return value;
48
105
  }
49
106
  }
107
+ function useAmPm(hass) {
108
+ // https://github.com/home-assistant/frontend/blob/52ac052baf139e94b7ed6891eb0beace7e2f47d3/src/common/datetime/use_am_pm.ts
109
+ if (hass.locale.time_format === TimeFormat.language ||
110
+ hass.locale.time_format === TimeFormat.system) {
111
+ const testLanguage = hass.locale.time_format === TimeFormat.language
112
+ ? hass.locale.language
113
+ : undefined;
114
+ const test = new Date('January 1, 2023 22:00:00').toLocaleString(testLanguage);
115
+ return test.includes('10');
116
+ }
117
+ return hass.locale.time_format === TimeFormat.am_pm;
118
+ }
119
+ function resolveTimeZone(hass) {
120
+ // https://github.com/home-assistant/frontend/blob/dev/src/common/datetime/resolve-time-zone.ts#L9
121
+ const intlTZ = Intl.DateTimeFormat?.().resolvedOptions?.().timeZone;
122
+ const localTZ = intlTZ ?? 'UTC';
123
+ return hass.locale.time_zone === TimeZone.local && intlTZ
124
+ ? localTZ
125
+ : hass.config.time_zone;
126
+ }
127
+ export function getTimeFormatter(hass) {
128
+ const AMPM = useAmPm(hass);
129
+ const options = {
130
+ hour: AMPM ? 'numeric' : '2-digit',
131
+ minute: '2-digit',
132
+ hourCycle: AMPM ? 'h12' : 'h23',
133
+ timeZone: resolveTimeZone(hass),
134
+ };
135
+ return new Intl.DateTimeFormat(hass.locale.language ?? hass.language, options);
136
+ }
50
137
  export function time_translated(value) {
51
138
  try {
52
- return window.haNunjucks.timeFormat.format(value.jsDate);
139
+ let time;
140
+ if (typeof value === 'string') {
141
+ time = new Date(`1970-01-01T${value}`);
142
+ }
143
+ else if (value instanceof Date) {
144
+ time = value;
145
+ }
146
+ else {
147
+ time = value.jsDate;
148
+ }
149
+ return window.haNunjucks.timeFormat.format(time);
53
150
  }
54
151
  catch {
55
152
  return value;
56
153
  }
57
154
  }
58
- export function datetime_translated(value) {
155
+ export function datetime_translated(hass, value) {
59
156
  try {
60
- return window.haNunjucks.datetimeFormat.format(value.jsDate);
157
+ let datetime;
158
+ if (typeof value === 'string') {
159
+ datetime = new Date(value);
160
+ }
161
+ else if (value instanceof Date) {
162
+ datetime = value;
163
+ }
164
+ else {
165
+ datetime = value.jsDate;
166
+ }
167
+ return `${date_translated(hass, datetime)} at ${time_translated(datetime)}`;
61
168
  }
62
169
  catch {
63
170
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ha-nunjucks",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "Wrapper for nunjucks for use with Home Assistant frontend custom components to render templates",
5
5
  "main": "./dist/index.js",
6
6
  "files": [