mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
feat(i18n): localize time offset descriptions and config flow strings
Added complete localization support for time offset descriptions:
- Convert hardcoded English strings "(X days ago)" to translatable keys
- Add time_units translations (day/days, hour/hours, minute/minutes, ago, now)
- Support singular/plural forms in all 5 languages (de, en, nb, nl, sv)
- German: Proper Dativ case "Tagen" with preposition "vor"
- Compact format for mixed offsets: "7 Tagen - 02:30"
Config flow improvements:
- Replace hardcoded "Enter new API token" with translated "Add new Tibber account API token"
- Use get_translation() for account_choice dropdown labels
- Fix SelectOptionDict usage (no mixing with translation_key parameter)
- Convert days slider from float to int (prevents "2.0 Tage" display)
- DurationSelector: default {"hours": 0, "minutes": 0} to fix validation errors
Translation keys added:
- selector.account_choice.options.new_token
- time_units (day, days, hour, hours, minute, minutes, ago, now)
- config.step.time_offset_description guidance text
Impact: Config flow works fully translated in all 5 languages with proper grammar.
This commit is contained in:
parent
bab72ac341
commit
2449c28a88
7 changed files with 1072 additions and 180 deletions
|
|
@ -1,126 +1,306 @@
|
||||||
"""Subentry config flow for adding additional Tibber homes."""
|
"""Subentry config flow for creating time-travel views."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from custom_components.tibber_prices.config_flow_handlers.schemas import (
|
import voluptuous as vol
|
||||||
get_select_home_schema,
|
|
||||||
get_subentry_init_schema,
|
|
||||||
)
|
|
||||||
from custom_components.tibber_prices.const import (
|
from custom_components.tibber_prices.const import (
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
CONF_VIRTUAL_TIME_OFFSET_DAYS,
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
CONF_VIRTUAL_TIME_OFFSET_HOURS,
|
||||||
|
CONF_VIRTUAL_TIME_OFFSET_MINUTES,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigSubentryFlow, SubentryFlowResult
|
from homeassistant.config_entries import ConfigSubentryFlow, SubentryFlowResult
|
||||||
from homeassistant.helpers.selector import SelectOptionDict
|
from homeassistant.helpers.selector import (
|
||||||
|
DurationSelector,
|
||||||
|
DurationSelectorConfig,
|
||||||
|
NumberSelector,
|
||||||
|
NumberSelectorConfig,
|
||||||
|
NumberSelectorMode,
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
SelectSelectorMode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TibberPricesSubentryFlowHandler(ConfigSubentryFlow):
|
class TibberPricesSubentryFlowHandler(ConfigSubentryFlow):
|
||||||
"""Handle subentry flows for tibber_prices."""
|
"""Handle subentry flows for tibber_prices (time-travel views)."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the subentry flow handler."""
|
||||||
|
super().__init__()
|
||||||
|
self._selected_parent_entry_id: str | None = None
|
||||||
|
|
||||||
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> SubentryFlowResult:
|
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> SubentryFlowResult:
|
||||||
"""User flow to add a new home."""
|
"""Step 1: Select which config entry should get a time-travel subentry."""
|
||||||
parent_entry = self._get_entry()
|
errors: dict[str, str] = {}
|
||||||
if not parent_entry or not hasattr(parent_entry, "runtime_data") or not parent_entry.runtime_data:
|
|
||||||
return self.async_abort(reason="no_parent_entry")
|
|
||||||
|
|
||||||
coordinator = parent_entry.runtime_data.coordinator
|
|
||||||
|
|
||||||
# Force refresh user data to get latest homes from Tibber API
|
|
||||||
await coordinator.refresh_user_data()
|
|
||||||
|
|
||||||
homes = coordinator.get_user_homes()
|
|
||||||
if not homes:
|
|
||||||
return self.async_abort(reason="no_available_homes")
|
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
selected_home_id = user_input["home_id"]
|
self._selected_parent_entry_id = user_input["parent_entry_id"]
|
||||||
selected_home = next((home for home in homes if home["id"] == selected_home_id), None)
|
return await self.async_step_time_offset()
|
||||||
|
|
||||||
if not selected_home:
|
# Get all main config entries (not subentries)
|
||||||
return self.async_abort(reason="home_not_found")
|
# Subentries have "_hist_" in their unique_id
|
||||||
|
main_entries = [
|
||||||
home_title = self._get_home_title(selected_home)
|
entry
|
||||||
home_id = selected_home["id"]
|
|
||||||
|
|
||||||
return self.async_create_entry(
|
|
||||||
title=home_title,
|
|
||||||
data={
|
|
||||||
"home_id": home_id,
|
|
||||||
"home_data": selected_home,
|
|
||||||
},
|
|
||||||
description=f"Subentry for {home_title}",
|
|
||||||
description_placeholders={"home_id": home_id},
|
|
||||||
unique_id=home_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get existing home IDs by checking all entries (parent + subentries)
|
|
||||||
existing_home_ids = {
|
|
||||||
entry.data["home_id"]
|
|
||||||
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
||||||
if entry.data.get("home_id")
|
if entry.unique_id and "_hist_" not in entry.unique_id
|
||||||
}
|
]
|
||||||
|
|
||||||
# Also include parent entry's home_id if it exists
|
if not main_entries:
|
||||||
if parent_entry.data.get("home_id"):
|
return self.async_abort(reason="no_main_entries")
|
||||||
existing_home_ids.add(parent_entry.data["home_id"])
|
|
||||||
|
|
||||||
available_homes = [home for home in homes if home["id"] not in existing_home_ids]
|
# Build options for entry selection
|
||||||
|
entry_options = [
|
||||||
if not available_homes:
|
|
||||||
return self.async_abort(reason="no_available_homes")
|
|
||||||
|
|
||||||
home_options = [
|
|
||||||
SelectOptionDict(
|
SelectOptionDict(
|
||||||
value=home["id"],
|
value=entry.entry_id,
|
||||||
label=self._get_home_title(home),
|
label=f"{entry.title} ({entry.data.get('user_login', 'N/A')})",
|
||||||
)
|
)
|
||||||
for home in available_homes
|
for entry in main_entries
|
||||||
]
|
]
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=get_select_home_schema(home_options),
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("parent_entry_id"): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=entry_options,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
description_placeholders={},
|
description_placeholders={},
|
||||||
errors={},
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_home_title(self, home: dict) -> str:
|
async def async_step_time_offset(self, user_input: dict[str, Any] | None = None) -> SubentryFlowResult:
|
||||||
"""Generate a user-friendly title for a home."""
|
"""Step 2: Configure time offset for the time-travel view."""
|
||||||
title = home.get("appNickname")
|
errors: dict[str, str] = {}
|
||||||
if title and title.strip():
|
|
||||||
return title.strip()
|
|
||||||
|
|
||||||
address = home.get("address", {})
|
if user_input is not None:
|
||||||
if address:
|
# Extract values (convert days to int to avoid float from slider)
|
||||||
parts = []
|
offset_days = int(user_input.get(CONF_VIRTUAL_TIME_OFFSET_DAYS, 0))
|
||||||
if address.get("address1"):
|
|
||||||
parts.append(address["address1"])
|
|
||||||
if address.get("city"):
|
|
||||||
parts.append(address["city"])
|
|
||||||
if parts:
|
|
||||||
return ", ".join(parts)
|
|
||||||
|
|
||||||
return home.get("id", "Unknown Home")
|
# DurationSelector returns dict with 'hours', 'minutes', and 'seconds' keys
|
||||||
|
# We normalize to minute precision (ignore seconds)
|
||||||
|
time_offset = user_input.get("time_offset", {})
|
||||||
|
offset_hours = -abs(int(time_offset.get("hours", 0))) # Always negative for historical data
|
||||||
|
offset_minutes = -abs(int(time_offset.get("minutes", 0))) # Always negative for historical data
|
||||||
|
# Note: Seconds are ignored - we only support minute-level precision
|
||||||
|
|
||||||
|
# Validate that at least one offset is negative (historical data only)
|
||||||
|
if offset_days >= 0 and offset_hours >= 0 and offset_minutes >= 0:
|
||||||
|
errors["base"] = "no_time_offset"
|
||||||
|
|
||||||
|
if not errors:
|
||||||
|
# Get parent entry
|
||||||
|
if not self._selected_parent_entry_id:
|
||||||
|
return self.async_abort(reason="parent_entry_not_found")
|
||||||
|
|
||||||
|
parent_entry = self.hass.config_entries.async_get_entry(self._selected_parent_entry_id)
|
||||||
|
if not parent_entry:
|
||||||
|
return self.async_abort(reason="parent_entry_not_found")
|
||||||
|
|
||||||
|
# Get home data from parent entry
|
||||||
|
home_id = parent_entry.data.get("home_id")
|
||||||
|
home_data = parent_entry.data.get("home_data", {})
|
||||||
|
user_login = parent_entry.data.get("user_login", "N/A")
|
||||||
|
|
||||||
|
# Build unique_id with time offset signature
|
||||||
|
offset_str = f"d{offset_days}h{offset_hours}m{offset_minutes}"
|
||||||
|
user_id = parent_entry.unique_id.split("_")[0] if parent_entry.unique_id else home_id
|
||||||
|
unique_id = f"{user_id}_{home_id}_hist_{offset_str}"
|
||||||
|
|
||||||
|
# Check if this exact time offset already exists
|
||||||
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
|
if entry.unique_id == unique_id:
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
|
# No duplicate found - create the entry
|
||||||
|
offset_desc = self._format_offset_description(offset_days, offset_hours, offset_minutes)
|
||||||
|
subentry_title = f"{parent_entry.title} ({offset_desc})"
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=subentry_title,
|
||||||
|
data={
|
||||||
|
"home_id": home_id,
|
||||||
|
"home_data": home_data,
|
||||||
|
"user_login": user_login,
|
||||||
|
CONF_VIRTUAL_TIME_OFFSET_DAYS: offset_days,
|
||||||
|
CONF_VIRTUAL_TIME_OFFSET_HOURS: offset_hours,
|
||||||
|
CONF_VIRTUAL_TIME_OFFSET_MINUTES: offset_minutes,
|
||||||
|
},
|
||||||
|
description=f"Time-travel view: {offset_desc}",
|
||||||
|
description_placeholders={"offset": offset_desc},
|
||||||
|
unique_id=unique_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="time_offset",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_VIRTUAL_TIME_OFFSET_DAYS, default=0): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
min=-374,
|
||||||
|
max=0,
|
||||||
|
step=1,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
vol.Optional("time_offset", default={"hours": 0, "minutes": 0}): DurationSelector(
|
||||||
|
DurationSelectorConfig(
|
||||||
|
allow_negative=False, # We handle sign automatically
|
||||||
|
enable_day=False, # Days are handled by the slider above
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
description_placeholders={},
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _format_offset_description(self, days: int, hours: int, minutes: int) -> str:
|
||||||
|
"""
|
||||||
|
Format time offset into human-readable description.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
-7, 0, 0 -> "7 days ago" (English) / "vor 7 Tagen" (German)
|
||||||
|
0, -2, 0 -> "2 hours ago" (English) / "vor 2 Stunden" (German)
|
||||||
|
-7, -2, -30 -> "7 days - 02:30" (compact format when time is added)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get translations loaded by Home Assistant
|
||||||
|
standard_translations_key = f"{DOMAIN}_standard_translations_{self.hass.config.language}"
|
||||||
|
translations = self.hass.data.get(standard_translations_key, {})
|
||||||
|
time_units = translations.get("config_subentries", {}).get("home", {}).get("time_units", {})
|
||||||
|
|
||||||
|
# Fallback to English if translations not available
|
||||||
|
if not time_units:
|
||||||
|
time_units = {
|
||||||
|
"day": "{count} day",
|
||||||
|
"days": "{count} days",
|
||||||
|
"hour": "{count} hour",
|
||||||
|
"hours": "{count} hours",
|
||||||
|
"minute": "{count} minute",
|
||||||
|
"minutes": "{count} minutes",
|
||||||
|
"ago": "{parts} ago",
|
||||||
|
"now": "now",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we have hours or minutes (need compact format)
|
||||||
|
has_time = hours != 0 or minutes != 0
|
||||||
|
|
||||||
|
if days != 0 and has_time:
|
||||||
|
# Compact format: "7 days - 02:30"
|
||||||
|
count = abs(days)
|
||||||
|
unit_key = "days" if count != 1 else "day"
|
||||||
|
day_part = time_units[unit_key].format(count=count)
|
||||||
|
time_part = f"{abs(hours):02d}:{abs(minutes):02d}"
|
||||||
|
return f"{day_part} - {time_part}"
|
||||||
|
|
||||||
|
# Standard format: separate parts with spaces
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
if days != 0:
|
||||||
|
count = abs(days)
|
||||||
|
unit_key = "days" if count != 1 else "day"
|
||||||
|
parts.append(time_units[unit_key].format(count=count))
|
||||||
|
|
||||||
|
if hours != 0:
|
||||||
|
count = abs(hours)
|
||||||
|
unit_key = "hours" if count != 1 else "hour"
|
||||||
|
parts.append(time_units[unit_key].format(count=count))
|
||||||
|
|
||||||
|
if minutes != 0:
|
||||||
|
count = abs(minutes)
|
||||||
|
unit_key = "minutes" if count != 1 else "minute"
|
||||||
|
parts.append(time_units[unit_key].format(count=count))
|
||||||
|
|
||||||
|
if not parts:
|
||||||
|
return time_units.get("now", "now")
|
||||||
|
|
||||||
|
# All offsets should be negative (historical data only)
|
||||||
|
# Join parts with space and apply "ago" template
|
||||||
|
return time_units["ago"].format(parts=" ".join(parts))
|
||||||
|
|
||||||
async def async_step_init(self, user_input: dict | None = None) -> SubentryFlowResult:
|
async def async_step_init(self, user_input: dict | None = None) -> SubentryFlowResult:
|
||||||
"""Manage the options for a subentry."""
|
"""Manage the options for an existing subentry (time-travel settings)."""
|
||||||
subentry = self._get_reconfigure_subentry()
|
subentry = self._get_reconfigure_subentry()
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
return self.async_update_and_abort(
|
# Extract values (convert days to int to avoid float from slider)
|
||||||
self._get_entry(),
|
offset_days = int(user_input.get(CONF_VIRTUAL_TIME_OFFSET_DAYS, 0))
|
||||||
subentry,
|
|
||||||
data_updates=user_input,
|
|
||||||
)
|
|
||||||
|
|
||||||
extended_descriptions = subentry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS)
|
# DurationSelector returns dict with 'hours', 'minutes', and 'seconds' keys
|
||||||
|
# We normalize to minute precision (ignore seconds)
|
||||||
|
time_offset = user_input.get("time_offset", {})
|
||||||
|
offset_hours = -abs(int(time_offset.get("hours", 0))) # Always negative for historical data
|
||||||
|
offset_minutes = -abs(int(time_offset.get("minutes", 0))) # Always negative for historical data
|
||||||
|
# Note: Seconds are ignored - we only support minute-level precision
|
||||||
|
|
||||||
|
# Validate that at least one offset is negative (historical data only)
|
||||||
|
if offset_days >= 0 and offset_hours >= 0 and offset_minutes >= 0:
|
||||||
|
errors["base"] = "no_time_offset"
|
||||||
|
else:
|
||||||
|
# Get parent entry to extract home_id and user_id
|
||||||
|
parent_entry = self._get_entry()
|
||||||
|
home_id = parent_entry.data.get("home_id")
|
||||||
|
|
||||||
|
# Build new unique_id with updated offset signature
|
||||||
|
offset_str = f"d{offset_days}h{offset_hours}m{offset_minutes}"
|
||||||
|
user_id = parent_entry.unique_id.split("_")[0] if parent_entry.unique_id else home_id
|
||||||
|
new_unique_id = f"{user_id}_{home_id}_hist_{offset_str}"
|
||||||
|
|
||||||
|
# Generate new title with updated offset description
|
||||||
|
offset_desc = self._format_offset_description(offset_days, offset_hours, offset_minutes)
|
||||||
|
# Extract parent title (remove old offset description in parentheses)
|
||||||
|
parent_title = parent_entry.title.split(" (")[0] if " (" in parent_entry.title else parent_entry.title
|
||||||
|
new_title = f"{parent_title} ({offset_desc})"
|
||||||
|
|
||||||
|
return self.async_update_and_abort(
|
||||||
|
parent_entry,
|
||||||
|
subentry,
|
||||||
|
unique_id=new_unique_id,
|
||||||
|
title=new_title,
|
||||||
|
data_updates=user_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
offset_days = subentry.data.get(CONF_VIRTUAL_TIME_OFFSET_DAYS, 0)
|
||||||
|
offset_hours = subentry.data.get(CONF_VIRTUAL_TIME_OFFSET_HOURS, 0)
|
||||||
|
offset_minutes = subentry.data.get(CONF_VIRTUAL_TIME_OFFSET_MINUTES, 0)
|
||||||
|
|
||||||
|
# Prepare time offset dict for DurationSelector (always positive, we negate on save)
|
||||||
|
time_offset_dict = {"hours": 0, "minutes": 0} # Default to zeros
|
||||||
|
if offset_hours != 0:
|
||||||
|
time_offset_dict["hours"] = abs(offset_hours)
|
||||||
|
if offset_minutes != 0:
|
||||||
|
time_offset_dict["minutes"] = abs(offset_minutes)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="init",
|
step_id="init",
|
||||||
data_schema=get_subentry_init_schema(extended_descriptions=extended_descriptions),
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_VIRTUAL_TIME_OFFSET_DAYS, default=offset_days): NumberSelector(
|
||||||
|
NumberSelectorConfig(
|
||||||
|
mode=NumberSelectorMode.SLIDER,
|
||||||
|
min=-374,
|
||||||
|
max=0,
|
||||||
|
step=1,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
vol.Optional("time_offset", default=time_offset_dict): DurationSelector(
|
||||||
|
DurationSelectorConfig(
|
||||||
|
allow_negative=False, # We handle sign automatically
|
||||||
|
enable_day=False, # Days are handled by the slider above
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from custom_components.tibber_prices.config_flow_handlers.options_flow import (
|
from custom_components.tibber_prices.config_flow_handlers.options_flow import (
|
||||||
TibberPricesOptionsFlowHandler,
|
TibberPricesOptionsFlowHandler,
|
||||||
)
|
)
|
||||||
|
|
@ -20,7 +23,7 @@ from custom_components.tibber_prices.config_flow_handlers.validators import (
|
||||||
TibberPricesInvalidAuthError,
|
TibberPricesInvalidAuthError,
|
||||||
validate_api_token,
|
validate_api_token,
|
||||||
)
|
)
|
||||||
from custom_components.tibber_prices.const import DOMAIN, LOGGER
|
from custom_components.tibber_prices.const import DOMAIN, LOGGER, get_translation
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
ConfigFlow,
|
ConfigFlow,
|
||||||
|
|
@ -29,13 +32,18 @@ from homeassistant.config_entries import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.selector import SelectOptionDict
|
from homeassistant.helpers.selector import (
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
SelectSelectorMode,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from homeassistant.config_entries import ConfigSubentryFlow
|
from homeassistant.config_entries import ConfigSubentryFlow
|
||||||
|
|
||||||
|
|
||||||
class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Config flow for tibber_prices."""
|
"""Config flow for tibber_prices."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
@ -132,7 +140,110 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
self,
|
self,
|
||||||
user_input: dict | None = None,
|
user_input: dict | None = None,
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initialized by the user. Only ask for access token."""
|
"""Handle a flow initialized by the user. Choose account or enter new token."""
|
||||||
|
# Get existing accounts
|
||||||
|
existing_entries = self.hass.config_entries.async_entries(DOMAIN)
|
||||||
|
|
||||||
|
# If there are existing accounts, offer choice
|
||||||
|
if existing_entries and user_input is None:
|
||||||
|
return await self.async_step_account_choice()
|
||||||
|
|
||||||
|
# Otherwise, go directly to token input
|
||||||
|
return await self.async_step_new_token(user_input)
|
||||||
|
|
||||||
|
async def async_step_account_choice(
|
||||||
|
self,
|
||||||
|
user_input: dict | None = None,
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Let user choose between existing account or new token."""
|
||||||
|
if user_input is not None:
|
||||||
|
choice = user_input["account_choice"]
|
||||||
|
|
||||||
|
if choice == "new_token":
|
||||||
|
return await self.async_step_new_token()
|
||||||
|
|
||||||
|
# User selected an existing account - copy its token
|
||||||
|
selected_entry_id = choice
|
||||||
|
selected_entry = next(
|
||||||
|
(
|
||||||
|
entry
|
||||||
|
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
||||||
|
if entry.entry_id == selected_entry_id
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not selected_entry:
|
||||||
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
|
# Copy token from selected entry and proceed
|
||||||
|
access_token = selected_entry.data.get(CONF_ACCESS_TOKEN)
|
||||||
|
if not access_token:
|
||||||
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
|
return await self.async_step_new_token({CONF_ACCESS_TOKEN: access_token})
|
||||||
|
|
||||||
|
# Build options: unique user accounts (grouped by user_id) + "New Token" option
|
||||||
|
existing_entries = self.hass.config_entries.async_entries(DOMAIN)
|
||||||
|
|
||||||
|
# Group entries by user_id to show unique accounts
|
||||||
|
# Minimum parts in unique_id format: user_id_home_id
|
||||||
|
min_unique_id_parts = 2
|
||||||
|
|
||||||
|
seen_users = {}
|
||||||
|
for entry in existing_entries:
|
||||||
|
# Extract user_id from unique_id (format: user_id_home_id or user_id_home_id_sub/hist_...)
|
||||||
|
unique_id = entry.unique_id
|
||||||
|
if unique_id:
|
||||||
|
# Split by underscore and take first part as user_id
|
||||||
|
parts = unique_id.split("_")
|
||||||
|
if len(parts) >= min_unique_id_parts:
|
||||||
|
user_id = parts[0]
|
||||||
|
if user_id not in seen_users:
|
||||||
|
seen_users[user_id] = entry
|
||||||
|
|
||||||
|
# Build dropdown options from unique user accounts
|
||||||
|
account_options = [
|
||||||
|
SelectOptionDict(
|
||||||
|
value=entry.entry_id,
|
||||||
|
label=f"{entry.title} ({entry.data.get('user_login', 'N/A')})",
|
||||||
|
)
|
||||||
|
for entry in seen_users.values()
|
||||||
|
]
|
||||||
|
# Add "new_token" option with translated label
|
||||||
|
new_token_label = (
|
||||||
|
get_translation(
|
||||||
|
["selector", "account_choice", "options", "new_token"],
|
||||||
|
self.hass.config.language,
|
||||||
|
)
|
||||||
|
or "Add new Tibber account API token"
|
||||||
|
)
|
||||||
|
account_options.append(
|
||||||
|
SelectOptionDict(
|
||||||
|
value="new_token",
|
||||||
|
label=new_token_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="account_choice",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("account_choice"): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=account_options,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_new_token(
|
||||||
|
self,
|
||||||
|
user_input: dict | None = None,
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle token input (new or copied from existing account)."""
|
||||||
_errors = {}
|
_errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
|
|
@ -159,9 +270,6 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
LOGGER.debug("Viewer data received: %s", viewer)
|
LOGGER.debug("Viewer data received: %s", viewer)
|
||||||
|
|
||||||
await self.async_set_unique_id(unique_id=str(user_id))
|
|
||||||
self._abort_if_unique_id_configured()
|
|
||||||
|
|
||||||
# Store viewer data in the flow for use in the next step
|
# Store viewer data in the flow for use in the next step
|
||||||
self._viewer = viewer
|
self._viewer = viewer
|
||||||
self._access_token = user_input[CONF_ACCESS_TOKEN]
|
self._access_token = user_input[CONF_ACCESS_TOKEN]
|
||||||
|
|
@ -173,25 +281,94 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
return await self.async_step_select_home()
|
return await self.async_step_select_home()
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="new_token",
|
||||||
data_schema=get_user_schema((user_input or {}).get(CONF_ACCESS_TOKEN)),
|
data_schema=get_user_schema((user_input or {}).get(CONF_ACCESS_TOKEN)),
|
||||||
errors=_errors,
|
errors=_errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_select_home(self, user_input: dict | None = None) -> ConfigFlowResult:
|
async def async_step_select_home(self, user_input: dict | None = None) -> ConfigFlowResult: # noqa: PLR0911
|
||||||
"""Handle home selection during initial setup."""
|
"""Handle home selection during initial setup."""
|
||||||
homes = self._viewer.get("homes", []) if self._viewer else []
|
homes = self._viewer.get("homes", []) if self._viewer else []
|
||||||
|
|
||||||
if not homes:
|
if not homes:
|
||||||
return self.async_abort(reason="unknown")
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
|
# Filter out already configured homes
|
||||||
|
configured_home_ids = {
|
||||||
|
entry.data.get("home_id")
|
||||||
|
for entry in self.hass.config_entries.async_entries(DOMAIN)
|
||||||
|
if entry.data.get("home_id")
|
||||||
|
}
|
||||||
|
available_homes = [home for home in homes if home["id"] not in configured_home_ids]
|
||||||
|
|
||||||
|
# If no homes available, abort
|
||||||
|
if not available_homes:
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
selected_home_id = user_input["home_id"]
|
selected_home_id = user_input["home_id"]
|
||||||
selected_home = next((home for home in homes if home["id"] == selected_home_id), None)
|
selected_home = next((home for home in available_homes if home["id"] == selected_home_id), None)
|
||||||
|
|
||||||
if not selected_home:
|
if not selected_home:
|
||||||
return self.async_abort(reason="unknown")
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
|
# Validate that home has an active or future subscription
|
||||||
|
subscription_status = self._get_subscription_status(selected_home)
|
||||||
|
|
||||||
|
if subscription_status == "none":
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="select_home",
|
||||||
|
data_schema=get_select_home_schema(
|
||||||
|
[
|
||||||
|
SelectOptionDict(
|
||||||
|
value=home["id"],
|
||||||
|
label=self._get_home_title_with_status(home),
|
||||||
|
)
|
||||||
|
for home in available_homes
|
||||||
|
]
|
||||||
|
),
|
||||||
|
errors={"home_id": "no_active_subscription"},
|
||||||
|
)
|
||||||
|
|
||||||
|
if subscription_status == "expired":
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="select_home",
|
||||||
|
data_schema=get_select_home_schema(
|
||||||
|
[
|
||||||
|
SelectOptionDict(
|
||||||
|
value=home["id"],
|
||||||
|
label=self._get_home_title_with_status(home),
|
||||||
|
)
|
||||||
|
for home in available_homes
|
||||||
|
]
|
||||||
|
),
|
||||||
|
errors={"home_id": "subscription_expired"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set unique_id to user_id + home_id combination
|
||||||
|
# This allows multiple homes per user account (single-home architecture)
|
||||||
|
unique_id = f"{self._user_id}_{selected_home_id}"
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
# Note: This check is now redundant since we filter available_homes upfront,
|
||||||
|
# but kept as defensive programming in case of race conditions
|
||||||
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
|
if entry.data.get("home_id") == selected_home_id:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="select_home",
|
||||||
|
data_schema=get_select_home_schema(
|
||||||
|
[
|
||||||
|
SelectOptionDict(
|
||||||
|
value=home["id"],
|
||||||
|
label=self._get_home_title(home),
|
||||||
|
)
|
||||||
|
for home in available_homes
|
||||||
|
]
|
||||||
|
),
|
||||||
|
errors={"home_id": "home_already_configured"},
|
||||||
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
CONF_ACCESS_TOKEN: self._access_token or "",
|
CONF_ACCESS_TOKEN: self._access_token or "",
|
||||||
"home_id": selected_home_id,
|
"home_id": selected_home_id,
|
||||||
|
|
@ -200,8 +377,11 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"user_login": self._user_login or "N/A",
|
"user_login": self._user_login or "N/A",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Generate entry title from home address (not appNickname)
|
||||||
|
entry_title = self._get_entry_title(selected_home)
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self._user_name or "Unknown User",
|
title=entry_title,
|
||||||
data=data,
|
data=data,
|
||||||
description=f"{self._user_login} ({self._user_id})",
|
description=f"{self._user_login} ({self._user_id})",
|
||||||
)
|
)
|
||||||
|
|
@ -209,9 +389,9 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
home_options = [
|
home_options = [
|
||||||
SelectOptionDict(
|
SelectOptionDict(
|
||||||
value=home["id"],
|
value=home["id"],
|
||||||
label=self._get_home_title(home),
|
label=self._get_home_title_with_status(home),
|
||||||
)
|
)
|
||||||
for home in homes
|
for home in available_homes
|
||||||
]
|
]
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
|
@ -234,9 +414,138 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
return home_ids
|
return home_ids
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_subscription_status(home: dict) -> str:
|
||||||
|
"""
|
||||||
|
Check subscription status of home.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- "active": Subscription is currently active
|
||||||
|
- "future": Subscription exists but starts in the future (validFrom > now)
|
||||||
|
- "expired": Subscription exists but has ended (validTo < now)
|
||||||
|
- "none": No subscription exists
|
||||||
|
|
||||||
|
"""
|
||||||
|
subscription = home.get("currentSubscription")
|
||||||
|
|
||||||
|
if subscription is None or subscription.get("status") is None:
|
||||||
|
return "none"
|
||||||
|
|
||||||
|
# Check validTo (contract end date)
|
||||||
|
valid_to = subscription.get("validTo")
|
||||||
|
if valid_to:
|
||||||
|
try:
|
||||||
|
valid_to_dt = datetime.fromisoformat(valid_to)
|
||||||
|
if valid_to_dt < datetime.now(valid_to_dt.tzinfo):
|
||||||
|
return "expired"
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
pass # If parsing fails, continue with other checks
|
||||||
|
|
||||||
|
# Check validFrom (contract start date)
|
||||||
|
valid_from = subscription.get("validFrom")
|
||||||
|
if valid_from:
|
||||||
|
try:
|
||||||
|
valid_from_dt = datetime.fromisoformat(valid_from)
|
||||||
|
if valid_from_dt > datetime.now(valid_from_dt.tzinfo):
|
||||||
|
return "future"
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
pass # If parsing fails, assume active
|
||||||
|
|
||||||
|
return "active"
|
||||||
|
|
||||||
|
def _get_home_title_with_status(self, home: dict) -> str:
|
||||||
|
"""Generate a user-friendly title for a home with subscription status."""
|
||||||
|
base_title = self._get_home_title(home)
|
||||||
|
status = self._get_subscription_status(home)
|
||||||
|
|
||||||
|
if status == "none":
|
||||||
|
return f"{base_title} ⚠️ (No active contract)"
|
||||||
|
if status == "expired":
|
||||||
|
return f"{base_title} ⚠️ (Contract expired)"
|
||||||
|
if status == "future":
|
||||||
|
return f"{base_title} ⚠️ (Contract starts soon)"
|
||||||
|
|
||||||
|
return base_title
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_city_name(city: str) -> str:
|
||||||
|
"""
|
||||||
|
Format city name to title case.
|
||||||
|
|
||||||
|
Converts 'MÜNCHEN' to 'München', handles multi-word cities like
|
||||||
|
'BAD TÖLZ' -> 'Bad Tölz', and hyphenated cities like
|
||||||
|
'GARMISCH-PARTENKIRCHEN' -> 'Garmisch-Partenkirchen'.
|
||||||
|
"""
|
||||||
|
if not city:
|
||||||
|
return city
|
||||||
|
|
||||||
|
# Split by space and hyphen while preserving delimiters
|
||||||
|
words = []
|
||||||
|
current_word = ""
|
||||||
|
|
||||||
|
for char in city:
|
||||||
|
if char in (" ", "-"):
|
||||||
|
if current_word:
|
||||||
|
words.append(current_word)
|
||||||
|
words.append(char) # Preserve delimiter
|
||||||
|
current_word = ""
|
||||||
|
else:
|
||||||
|
current_word += char
|
||||||
|
|
||||||
|
if current_word: # Add last word
|
||||||
|
words.append(current_word)
|
||||||
|
|
||||||
|
# Capitalize first letter of each word (not delimiters)
|
||||||
|
formatted_words = []
|
||||||
|
for word in words:
|
||||||
|
if word in (" ", "-"):
|
||||||
|
formatted_words.append(word)
|
||||||
|
else:
|
||||||
|
# Capitalize first letter, lowercase rest
|
||||||
|
formatted_words.append(word.capitalize())
|
||||||
|
|
||||||
|
return "".join(formatted_words)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_entry_title(home: dict) -> str:
|
||||||
|
"""
|
||||||
|
Generate entry title from address (for config entry title).
|
||||||
|
|
||||||
|
Uses 'address1, City' format, e.g. 'Pählstraße 6B, München'.
|
||||||
|
Does NOT use appNickname (that's for _get_home_title).
|
||||||
|
"""
|
||||||
|
address = home.get("address", {})
|
||||||
|
|
||||||
|
if not address:
|
||||||
|
# Fallback to home ID if no address
|
||||||
|
return home.get("id", "Unknown Home")
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
# Always prefer address1
|
||||||
|
address1 = address.get("address1")
|
||||||
|
if address1 and address1.strip():
|
||||||
|
parts.append(address1.strip())
|
||||||
|
|
||||||
|
# Format city name (convert MÜNCHEN -> München)
|
||||||
|
city = address.get("city")
|
||||||
|
if city and city.strip():
|
||||||
|
formatted_city = TibberPricesConfigFlowHandler._format_city_name(city.strip())
|
||||||
|
parts.append(formatted_city)
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
# Final fallback
|
||||||
|
return home.get("id", "Unknown Home")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_home_title(home: dict) -> str:
|
def _get_home_title(home: dict) -> str:
|
||||||
"""Generate a user-friendly title for a home."""
|
"""
|
||||||
|
Generate a user-friendly title for a home (for dropdown display).
|
||||||
|
|
||||||
|
Prefers appNickname, falls back to address.
|
||||||
|
"""
|
||||||
title = home.get("appNickname")
|
title = home.get("appNickname")
|
||||||
if title and title.strip():
|
if title and title.strip():
|
||||||
return title.strip()
|
return title.strip()
|
||||||
|
|
@ -247,7 +556,10 @@ class TibberPricesFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
if address.get("address1"):
|
if address.get("address1"):
|
||||||
parts.append(address["address1"])
|
parts.append(address["address1"])
|
||||||
if address.get("city"):
|
if address.get("city"):
|
||||||
parts.append(address["city"])
|
# Format city for display too
|
||||||
|
city = address["city"]
|
||||||
|
formatted_city = TibberPricesConfigFlowHandler._format_city_name(city)
|
||||||
|
parts.append(formatted_city)
|
||||||
if parts:
|
if parts:
|
||||||
return ", ".join(parts)
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
|
"account_choice": {
|
||||||
|
"title": "Konto wählen",
|
||||||
|
"description": "Du kannst ein weiteres Zuhause aus einem bestehenden Tibber-Konto hinzufügen oder einen neuen API-Token für ein anderes Konto eingeben.",
|
||||||
|
"data": {
|
||||||
|
"account_choice": "Konto"
|
||||||
|
},
|
||||||
|
"submit": "Weiter →"
|
||||||
|
},
|
||||||
|
"new_token": {
|
||||||
|
"title": "API-Token eingeben",
|
||||||
|
"description": "Richte Tibber Preisinformationen & Bewertungen ein.\n\nUm einen API-Zugriffstoken zu generieren, besuche https://developer.tibber.com.",
|
||||||
|
"data": {
|
||||||
|
"access_token": "API-Zugriffstoken"
|
||||||
|
},
|
||||||
|
"submit": "Token validieren"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Richte Tibber Preisinformationen & Bewertungen ein.\n\nUm einen API-Zugriffstoken zu generieren, besuche https://developer.tibber.com.",
|
"description": "Richte Tibber Preisinformationen & Bewertungen ein.\n\nUm einen API-Zugriffstoken zu generieren, besuche https://developer.tibber.com.",
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -40,15 +56,24 @@
|
||||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||||
"invalid_access_token": "Ungültiges Zugriffstoken",
|
"invalid_access_token": "Ungültiges Zugriffstoken",
|
||||||
"missing_homes": "Der neue Zugriffstoken hat keinen Zugriff auf alle konfigurierten Zuhause. Bitte verwende einen Zugriffstoken, der Zugriff auf die gleichen Tibber-Zuhause hat.",
|
"missing_homes": "Der neue Zugriffstoken hat keinen Zugriff auf alle konfigurierten Zuhause. Bitte verwende einen Zugriffstoken, der Zugriff auf die gleichen Tibber-Zuhause hat.",
|
||||||
"invalid_yaml_syntax": "Ungültige YAML-Syntax. Bitte prüfe Einrückung, Doppelpunkte und Sonderzeichen.",
|
"home_already_configured": "Dieses Zuhause ist bereits in einem anderen Eintrag konfiguriert. Jedes Zuhause kann nur einmal konfiguriert werden.",
|
||||||
|
"no_active_subscription": "Dieses Zuhause hat keinen aktiven Tibber-Vertrag. Nur Häuser mit aktivem Stromvertrag können zu Home Assistant hinzugefügt werden.",
|
||||||
|
"subscription_expired": "Der Tibber-Vertrag für dieses Zuhause ist abgelaufen. Nur Häuser mit aktivem oder zukünftigem Stromvertrag können zu Home Assistant hinzugefügt werden.",
|
||||||
|
"future_subscription_warning": "Hinweis: Der Tibber-Vertrag für dieses Zuhause hat noch nicht begonnen. Die Funktionalität ist möglicherweise eingeschränkt, bis der Vertrag aktiv wird.",
|
||||||
|
"invalid_yaml_syntax": "Ungültige YAML-Syntax. Bitte überprüfe Einrückungen, Doppelpunkte und Sonderzeichen.",
|
||||||
"invalid_yaml_structure": "YAML muss ein Dictionary/Objekt sein (Schlüssel: Wert-Paare), keine Liste oder reiner Text.",
|
"invalid_yaml_structure": "YAML muss ein Dictionary/Objekt sein (Schlüssel: Wert-Paare), keine Liste oder reiner Text.",
|
||||||
"service_call_failed": "Service-Aufruf-Validierung fehlgeschlagen: {error_detail}"
|
"service_call_failed": "Service-Aufruf-Validierung fehlgeschlagen: {error_detail}",
|
||||||
|
"missing_entry_id": "Eintrag-ID wird benötigt, wurde aber nicht bereitgestellt.",
|
||||||
|
"invalid_entry_id": "Ungültige Eintrag-ID oder Eintrag nicht gefunden.",
|
||||||
|
"missing_home_id": "Home-ID fehlt in der Konfiguration.",
|
||||||
|
"user_data_not_available": "Benutzerdaten nicht verfügbar. Bitte aktualisiere zuerst die Benutzerdaten.",
|
||||||
|
"price_fetch_failed": "Preisdaten konnten nicht abgerufen werden. Bitte prüfe die Logs für Details."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Integration ist bereits konfiguriert",
|
"already_configured": "Alle verfügbaren Tibber-Zuhause sind bereits konfiguriert. Jedes Zuhause kann nur einmal konfiguriert werden.",
|
||||||
"entry_not_found": "Tibber Konfigurationseintrag nicht gefunden.",
|
"entry_not_found": "Tibber-Konfigurationseintrag nicht gefunden.",
|
||||||
"setup_complete": "Einrichtung abgeschlossen! Du kannst zusätzliche Optionen für Tibber Preise in den Integrationsoptionen ändern, nachdem du diesen Dialog geschlossen hast.",
|
"setup_complete": "Einrichtung abgeschlossen! Du kannst zusätzliche Optionen für Tibber Prices in den Integrationsoptionen nach Schließen dieses Dialogs ändern.",
|
||||||
"reauth_successful": "Erneute Authentifizierung erfolgreich. Die Integration wurde mit dem neuen Zugriffstoken aktualisiert."
|
"reauth_successful": "Neuauthentifizierung erfolgreich. Die Integration wurde mit dem neuen Zugriffstoken aktualisiert."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
|
@ -57,27 +82,59 @@
|
||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"home": {
|
"home": {
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Tibber Zuhause hinzufügen"
|
"user": "Zeitreise-Ansicht erstellen"
|
||||||
},
|
},
|
||||||
"title": "Tibber Zuhause hinzufügen",
|
"title": "Zeitreise-Ansicht erstellen",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Tibber Zuhause hinzufügen",
|
"title": "Konfigurationseintrag auswählen",
|
||||||
"description": "Wähle ein Zuhause aus, das du zu deiner Tibber-Integration hinzufügen möchtest.",
|
"description": "Wähle den Konfigurationseintrag aus, für den du eine Zeitreise-Ansicht erstellen möchtest.\n\n**Zeitreise-Ansichten** ermöglichen es dir, historische Preisdaten so anzuzeigen, als wären sie die aktuellen Daten. Dies ist nützlich zum Testen von Automatisierungen oder zur Analyse vergangener Preismuster.",
|
||||||
"data": {
|
"data": {
|
||||||
"home_id": "Zuhause"
|
"parent_entry_id": "Konfigurationseintrag"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time_offset": {
|
||||||
|
"title": "Zeitversatz konfigurieren",
|
||||||
|
"description": "Konfiguriere, wie weit zurück in der Zeit diese Ansicht reisen soll.\n\n**Empfohlen:** Verwende **≥2 Tage** Versatz, um Konflikte mit \"yesterday\"-Entitäten zu vermeiden, die ebenfalls historische Daten bereitstellen.\n\n**Beispiele:**\n• **-7 Tage**: Zeigt Preise von vor 7 Tagen\n• **-2 Tage, 3 Stunden**: Zeigt Preise von vor 2 Tagen und 3 Stunden\n• **-14 Tage**: Zeigt Preise von vor 2 Wochen",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Tage zurück",
|
||||||
|
"time_offset": "Zusätzlicher Zeitversatz"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Wie viele Tage in die Vergangenheit reisen. Slider-Bereich: 0 bis 374 Tage (≈1 Jahr). Empfohlen: ≥2 Tage, um Konflikte mit \"yesterday\"-Entitäten zu vermeiden.",
|
||||||
|
"time_offset": "Optionale Feinabstimmung: Füge Stunden und/oder Minuten zum Tagesversatz hinzu. Die Zeit wird automatisch subtrahiert (weiter zurück reisen). Hinweis: Sekunden werden ignoriert - nur minutengenaue Präzision wird unterstützt."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"title": "Zeitversatz neu konfigurieren",
|
||||||
|
"description": "Aktualisiere den Zeitversatz für diese Zeitreise-Ansicht.",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Tage zurück",
|
||||||
|
"time_offset": "Zusätzlicher Zeitversatz"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Wie viele Tage in die Vergangenheit reisen. Slider-Bereich: 0 bis 374 Tage (≈1 Jahr). Empfohlen: ≥2 Tage, um Konflikte mit \"yesterday\"-Entitäten zu vermeiden.",
|
||||||
|
"time_offset": "Optionale Feinabstimmung: Füge Stunden und/oder Minuten zum Tagesversatz hinzu. Die Zeit wird automatisch subtrahiert (weiter zurück reisen). Hinweis: Sekunden werden ignoriert - nur minutengenaue Präzision wird unterstützt."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"api_error": "Fehler beim Abrufen der Zuhause von der Tibber API"
|
"no_time_offset": "Mindestens ein Zeitversatzwert muss negativ sein (nur historische Daten)."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_parent_entry": "Übergeordneter Eintrag nicht gefunden",
|
"already_configured": "**Eine Zeitreise-Ansicht mit diesem exakten Zeitversatz existiert bereits.**\n\nBitte wähle einen anderen Versatz.",
|
||||||
"no_access_token": "Kein Zugriffstoken verfügbar",
|
"no_main_entries": "Keine Hauptkonfigurationseinträge gefunden. Füge zuerst ein Tibber-Zuhause hinzu.",
|
||||||
"home_not_found": "Ausgewähltes Zuhause nicht gefunden",
|
"parent_entry_not_found": "Ausgewählter Konfigurationseintrag nicht gefunden."
|
||||||
"api_error": "Fehler beim Abrufen der Zuhause von der Tibber API",
|
},
|
||||||
"no_available_homes": "Keine zusätzlichen Zuhause verfügbar. Alle Zuhause von deinem Tibber-Konto wurden bereits hinzugefügt."
|
"time_units": {
|
||||||
|
"day": "{count} Tag",
|
||||||
|
"days": "{count} Tagen",
|
||||||
|
"hour": "{count} Stunde",
|
||||||
|
"hours": "{count} Stunden",
|
||||||
|
"minute": "{count} Minute",
|
||||||
|
"minutes": "{count} Minuten",
|
||||||
|
"ago": "vor {parts}",
|
||||||
|
"now": "jetzt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -723,6 +780,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_price": {
|
||||||
|
"name": "Preisdaten abrufen",
|
||||||
|
"description": "Preisdaten für einen bestimmten Zeitraum mit automatischem Routing abrufen. Entwicklungs- und Test-Service für die price_info_for_range API-Funktion. Verwendet automatisch PRICE_INFO, PRICE_INFO_RANGE oder beide basierend auf der Zeitraumgrenze.",
|
||||||
|
"fields": {
|
||||||
|
"entry_id": {
|
||||||
|
"name": "Eintrag-ID",
|
||||||
|
"description": "Die Konfigurations-Eintrag-ID für die Tibber-Integration."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Startzeit",
|
||||||
|
"description": "Start des Zeitraums (inklusive, zeitzonenbewusst)."
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "Endzeit",
|
||||||
|
"description": "Ende des Zeitraums (exklusive, zeitzonenbewusst)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get_apexcharts_yaml": {
|
"get_apexcharts_yaml": {
|
||||||
"name": "ApexCharts-Karten-YAML abrufen",
|
"name": "ApexCharts-Karten-YAML abrufen",
|
||||||
"description": "Gibt einen fertigen YAML-Schnipsel für eine ApexCharts-Karte zurück, die Tibber-Preise für den ausgewählten Tag visualisiert. Verwende dies, um ganz einfach ein vorkonfiguriertes Diagramm zu deinem Dashboard hinzuzufügen. Das YAML verwendet den get_chartdata-Service für Daten.",
|
"description": "Gibt einen fertigen YAML-Schnipsel für eine ApexCharts-Karte zurück, die Tibber-Preise für den ausgewählten Tag visualisiert. Verwende dies, um ganz einfach ein vorkonfiguriertes Diagramm zu deinem Dashboard hinzuzufügen. Das YAML verwendet den get_chartdata-Service für Daten.",
|
||||||
|
|
@ -847,6 +922,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
"account_choice": {
|
||||||
|
"options": {
|
||||||
|
"new_token": "Neues Tibber-Konto per API-Token hinzufügen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"options": {
|
"options": {
|
||||||
"yesterday": "Gestern",
|
"yesterday": "Gestern",
|
||||||
|
|
@ -921,4 +1001,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Preisinformationen & Bewertungen"
|
"title": "Tibber Preisinformationen & Bewertungen"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
|
"account_choice": {
|
||||||
|
"title": "Choose Account",
|
||||||
|
"description": "You can add another home of an existing Tibber account or enter a new API token for a different account.",
|
||||||
|
"data": {
|
||||||
|
"account_choice": "Account"
|
||||||
|
},
|
||||||
|
"submit": "Continue →"
|
||||||
|
},
|
||||||
|
"new_token": {
|
||||||
|
"title": "Enter API Token",
|
||||||
|
"description": "Set up Tibber Price Information & Ratings.\n\nTo generate an API access token, visit https://developer.tibber.com.",
|
||||||
|
"data": {
|
||||||
|
"access_token": "API access token"
|
||||||
|
},
|
||||||
|
"submit": "Validate Token"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Set up Tibber Price Information & Ratings.\n\nTo generate an API access token, visit https://developer.tibber.com.",
|
"description": "Set up Tibber Price Information & Ratings.\n\nTo generate an API access token, visit https://developer.tibber.com.",
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -40,12 +56,21 @@
|
||||||
"cannot_connect": "Failed to connect",
|
"cannot_connect": "Failed to connect",
|
||||||
"invalid_access_token": "Invalid access token",
|
"invalid_access_token": "Invalid access token",
|
||||||
"missing_homes": "The new access token does not have access to all configured homes. Please use an access token that has access to the same Tibber homes.",
|
"missing_homes": "The new access token does not have access to all configured homes. Please use an access token that has access to the same Tibber homes.",
|
||||||
|
"home_already_configured": "This home is already configured in another entry. Each home can only be configured once.",
|
||||||
|
"no_active_subscription": "This home does not have an active Tibber contract. Only homes with active electricity contracts can be added to Home Assistant.",
|
||||||
|
"subscription_expired": "The Tibber contract for this home has expired. Only homes with active or future electricity contracts can be added to Home Assistant.",
|
||||||
|
"future_subscription_warning": "Note: This home's Tibber contract has not started yet. Functionality may be limited until the contract becomes active.",
|
||||||
"invalid_yaml_syntax": "Invalid YAML syntax. Please check indentation, colons, and special characters.",
|
"invalid_yaml_syntax": "Invalid YAML syntax. Please check indentation, colons, and special characters.",
|
||||||
"invalid_yaml_structure": "YAML must be a dictionary/object (key: value pairs), not a list or plain text.",
|
"invalid_yaml_structure": "YAML must be a dictionary/object (key: value pairs), not a list or plain text.",
|
||||||
"service_call_failed": "Service call validation failed: {error_detail}"
|
"service_call_failed": "Service call validation failed: {error_detail}",
|
||||||
|
"missing_entry_id": "Entry ID is required but was not provided.",
|
||||||
|
"invalid_entry_id": "Invalid entry ID or entry not found.",
|
||||||
|
"missing_home_id": "Home ID is missing from the configuration entry.",
|
||||||
|
"user_data_not_available": "User data is not available. Please refresh user data first.",
|
||||||
|
"price_fetch_failed": "Failed to fetch price data. Please check logs for details."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Integration is already configured",
|
"already_configured": "All available Tibber homes are already configured. Each home can only be configured once.",
|
||||||
"entry_not_found": "Tibber configuration entry not found.",
|
"entry_not_found": "Tibber configuration entry not found.",
|
||||||
"setup_complete": "Setup complete! You can change additional options for Tibber Prices in the integration's options after closing this dialog.",
|
"setup_complete": "Setup complete! You can change additional options for Tibber Prices in the integration's options after closing this dialog.",
|
||||||
"reauth_successful": "Reauthentication successful. The integration has been updated with the new access token."
|
"reauth_successful": "Reauthentication successful. The integration has been updated with the new access token."
|
||||||
|
|
@ -57,27 +82,59 @@
|
||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"home": {
|
"home": {
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Add Tibber Home"
|
"user": "Create Time-Travel View"
|
||||||
},
|
},
|
||||||
"title": "Add Tibber Home",
|
"title": "Create Time-Travel View",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Add Tibber Home",
|
"title": "Select Configuration Entry",
|
||||||
"description": "Select a home to add to your Tibber integration.\n\n**Note:** After adding this home, you can add additional homes from the integration's context menu by selecting \"Add Tibber Home\".",
|
"description": "Select the configuration entry for which you want to create a time-travel view.\n\n**Time-travel views** allow you to see historical price data as if it were the current time. This is useful for testing automations or analyzing past price patterns.",
|
||||||
"data": {
|
"data": {
|
||||||
"home_id": "Home"
|
"parent_entry_id": "Configuration Entry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time_offset": {
|
||||||
|
"title": "Configure Time Offset",
|
||||||
|
"description": "Configure how far back in time this view should travel.\n\n**Recommended:** Use **≥2 days** offset to avoid conflicts with \"yesterday\" entities that also provide historical data.\n\n**Examples:**\n• **-7 days**: View prices from 7 days ago\n• **-2 days, 3 hours**: View prices from 2 days and 3 hours ago\n• **-14 days**: View prices from 2 weeks ago",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Days Back",
|
||||||
|
"time_offset": "Additional Time Offset"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "How many days to travel back in time. Slider range: 0 to 374 days (≈1 year). Recommended: ≥2 days to avoid conflicts with \"yesterday\" entities.",
|
||||||
|
"time_offset": "Optional fine-tuning: Add hours and/or minutes to the day offset. The time will be automatically subtracted (travel back further). Note: Seconds are ignored - only minute-level precision is supported."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"title": "Reconfigure Time Offset",
|
||||||
|
"description": "Update the time offset for this time-travel view.",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Days Back",
|
||||||
|
"time_offset": "Additional Time Offset"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "How many days to travel back in time. Slider range: 0 to 374 days (≈1 year). Recommended: ≥2 days to avoid conflicts with \"yesterday\" entities.",
|
||||||
|
"time_offset": "Optional fine-tuning: Add hours and/or minutes to the day offset. The time will be automatically subtracted (travel back further). Note: Seconds are ignored - only minute-level precision is supported."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"api_error": "Failed to fetch homes from Tibber API"
|
"no_time_offset": "At least one time offset value must be negative (historical data only)."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_parent_entry": "Parent entry not found",
|
"already_configured": "**A time-travel view with this exact time offset already exists.**\n\nPlease choose a different offset.",
|
||||||
"no_access_token": "No access token available",
|
"no_main_entries": "No main configuration entries found. Please add a Tibber home first.",
|
||||||
"home_not_found": "Selected home not found",
|
"parent_entry_not_found": "Selected configuration entry not found."
|
||||||
"api_error": "Failed to fetch homes from Tibber API",
|
},
|
||||||
"no_available_homes": "No additional homes available to add. All homes from your Tibber account have already been added."
|
"time_units": {
|
||||||
|
"day": "{count} day",
|
||||||
|
"days": "{count} days",
|
||||||
|
"hour": "{count} hour",
|
||||||
|
"hours": "{count} hours",
|
||||||
|
"minute": "{count} minute",
|
||||||
|
"minutes": "{count} minutes",
|
||||||
|
"ago": "{parts} ago",
|
||||||
|
"now": "now"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -719,6 +776,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_price": {
|
||||||
|
"name": "Get Price Data",
|
||||||
|
"description": "Fetch price data for a specific time range with automatic routing. Development and testing service for the price_info_for_range API function. Automatically uses PRICE_INFO, PRICE_INFO_RANGE, or both based on the time range boundary.",
|
||||||
|
"fields": {
|
||||||
|
"entry_id": {
|
||||||
|
"name": "Entry ID",
|
||||||
|
"description": "The config entry ID for the Tibber integration."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Start Time",
|
||||||
|
"description": "Start of the time range (inclusive, timezone-aware)."
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "End Time",
|
||||||
|
"description": "End of the time range (exclusive, timezone-aware)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get_apexcharts_yaml": {
|
"get_apexcharts_yaml": {
|
||||||
"name": "Get ApexCharts Card YAML",
|
"name": "Get ApexCharts Card YAML",
|
||||||
"description": "Returns a ready-to-copy YAML snippet for an ApexCharts card visualizing Tibber Prices for the selected day. Use this to easily add a pre-configured chart to your dashboard. The YAML will use the get_chartdata service for data.",
|
"description": "Returns a ready-to-copy YAML snippet for an ApexCharts card visualizing Tibber Prices for the selected day. Use this to easily add a pre-configured chart to your dashboard. The YAML will use the get_chartdata service for data.",
|
||||||
|
|
@ -843,6 +918,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
"account_choice": {
|
||||||
|
"options": {
|
||||||
|
"new_token": "Add new Tibber account API token"
|
||||||
|
}
|
||||||
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"options": {
|
"options": {
|
||||||
"yesterday": "Yesterday",
|
"yesterday": "Yesterday",
|
||||||
|
|
@ -917,4 +997,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Price Information & Ratings"
|
"title": "Tibber Price Information & Ratings"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
|
"account_choice": {
|
||||||
|
"title": "Velg konto",
|
||||||
|
"description": "Du kan legge til et nytt hjem fra en eksisterende Tibber-konto eller skrive inn et nytt API-token for en annen konto.",
|
||||||
|
"data": {
|
||||||
|
"account_choice": "Konto"
|
||||||
|
},
|
||||||
|
"submit": "Fortsett →"
|
||||||
|
},
|
||||||
|
"new_token": {
|
||||||
|
"title": "Skriv inn API-token",
|
||||||
|
"description": "Sett opp Tibber Prisinformasjon & Vurderinger.\n\nFor å generere et API-tilgangstoken, besøk https://developer.tibber.com.",
|
||||||
|
"data": {
|
||||||
|
"access_token": "API-tilgangstoken"
|
||||||
|
},
|
||||||
|
"submit": "Valider token"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Sett opp Tibber Prisinformasjon & Vurderinger.\n\nFor å generere et API-tilgangstoken, besøk https://developer.tibber.com.",
|
"description": "Sett opp Tibber Prisinformasjon & Vurderinger.\n\nFor å generere et API-tilgangstoken, besøk https://developer.tibber.com.",
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -40,15 +56,24 @@
|
||||||
"cannot_connect": "Kunne ikke koble til",
|
"cannot_connect": "Kunne ikke koble til",
|
||||||
"invalid_access_token": "Ugyldig tilgangstoken",
|
"invalid_access_token": "Ugyldig tilgangstoken",
|
||||||
"missing_homes": "Det nye tilgangstokenet har ikke tilgang til alle konfigurerte hjem. Vennligst bruk et tilgangstoken som har tilgang til de samme Tibber-hjemmene.",
|
"missing_homes": "Det nye tilgangstokenet har ikke tilgang til alle konfigurerte hjem. Vennligst bruk et tilgangstoken som har tilgang til de samme Tibber-hjemmene.",
|
||||||
|
"home_already_configured": "Dette hjemmet er allerede konfigurert i en annen oppføring. Hvert hjem kan kun konfigureres én gang.",
|
||||||
|
"no_active_subscription": "Dette hjemmet har ikke en aktiv Tibber-kontrakt. Bare hjem med aktive strømkontrakter kan legges til Home Assistant.",
|
||||||
|
"subscription_expired": "Tibber-kontrakten for dette hjemmet har utløpt. Bare hjem med aktive eller fremtidige strømkontrakter kan legges til Home Assistant.",
|
||||||
|
"future_subscription_warning": "Merk: Tibber-kontrakten for dette hjemmet har ikke startet ennå. Funksjonaliteten kan være begrenset til kontrakten blir aktiv.",
|
||||||
"invalid_yaml_syntax": "Ugyldig YAML-syntaks. Vennligst sjekk innrykk, kolon og spesialtegn.",
|
"invalid_yaml_syntax": "Ugyldig YAML-syntaks. Vennligst sjekk innrykk, kolon og spesialtegn.",
|
||||||
"invalid_yaml_structure": "YAML må være en ordbok/objekt (nøkkel: verdi-par), ikke en liste eller ren tekst.",
|
"invalid_yaml_structure": "YAML må være en ordbok/objekt (nøkkel: verdi-par), ikke en liste eller ren tekst.",
|
||||||
"service_call_failed": "Service-kall validering feilet: {error_detail}"
|
"service_call_failed": "Service-kall validering feilet: {error_detail}",
|
||||||
|
"missing_entry_id": "Oppførings-ID er påkrevd, men ble ikke oppgitt.",
|
||||||
|
"invalid_entry_id": "Ugyldig oppførings-ID eller oppføring ikke funnet.",
|
||||||
|
"missing_home_id": "Hjem-ID mangler fra konfigurasjonsoppføringen.",
|
||||||
|
"user_data_not_available": "Brukerdata er ikke tilgjengelig. Vennligst oppdater brukerdata først.",
|
||||||
|
"price_fetch_failed": "Kunne ikke hente prisdata. Vennligst sjekk loggene for detaljer."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Integrasjonen er allerede konfigurert",
|
"already_configured": "Alle tilgjengelige Tibber-hjem er allerede konfigurert. Hvert hjem kan kun konfigureres én gang.",
|
||||||
"entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet.",
|
"entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet.",
|
||||||
"setup_complete": "Oppsett fullført! Du kan endre flere innstillinger for Tibber-priser i integrasjonens alternativer etter å ha lukket denne dialogen.",
|
"setup_complete": "Oppsett fullført! Du kan endre ytterligere alternativer for Tibber Prices i integrasjonens alternativer etter å ha lukket denne dialogen.",
|
||||||
"reauth_successful": "Autentisering vellykket. Integrasjonen er oppdatert med det nye tilgangstokenet."
|
"reauth_successful": "Ny autentisering vellykket. Integrasjonen har blitt oppdatert med det nye tilgangstokenet."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
|
@ -57,27 +82,59 @@
|
||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"home": {
|
"home": {
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Legg til Tibber-hjem"
|
"user": "Opprett tidsreisevisning"
|
||||||
},
|
},
|
||||||
"title": "Legg til Tibber-hjem",
|
"title": "Opprett tidsreisevisning",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Legg til Tibber-hjem",
|
"title": "Velg konfigurasjonsoppføring",
|
||||||
"description": "Velg et hjem å legge til i din Tibber-integrasjon.\n\n**Merk:** Etter å ha lagt til dette hjemmet, kan du legge til flere hjem fra integrasjonens kontekstmeny ved å velge \"Legg til Tibber-hjem\".",
|
"description": "Velg konfigurasjonsoppføringen du vil opprette en tidsreisevisning for.\n\n**Tidsreisevisninger** lar deg se historiske prisdata som om det var nåværende tid. Dette er nyttig for å teste automatiseringer eller analysere tidligere prismønstre.",
|
||||||
"data": {
|
"data": {
|
||||||
"home_id": "Hjem"
|
"parent_entry_id": "Konfigurasjonsoppføring"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time_offset": {
|
||||||
|
"title": "Konfigurer tidsforskyvning",
|
||||||
|
"description": "Konfigurer hvor langt tilbake i tid denne visningen skal reise.\n\n**Anbefalt:** Bruk **≥2 dager** forskyvning for å unngå konflikter med \"yesterday\"-entiteter som også gir historiske data.\n\n**Eksempler:**\n• **-7 dager**: Vis priser fra 7 dager siden\n• **-2 dager, 3 timer**: Vis priser fra 2 dager og 3 timer siden\n• **-14 dager**: Vis priser fra 2 uker siden",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dager tilbake",
|
||||||
|
"time_offset": "Ekstra tidsforskyvning"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hvor mange dager å reise tilbake i tid. Glidebryter-område: 0 til 374 dager (≈1 år). Anbefalt: ≥2 dager for å unngå konflikter med \"yesterday\"-entiteter.",
|
||||||
|
"time_offset": "Valgfri finjustering: Legg til timer og/eller minutter til dagesforskyvningen. Tiden trekkes automatisk fra (reis lenger tilbake). Merk: Sekunder ignoreres - kun minuttbasert presisjon støttes."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"title": "Konfigurer tidsforskyvning på nytt",
|
||||||
|
"description": "Oppdater tidsforskyvningen for denne tidsreisevisningen.",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dager tilbake",
|
||||||
|
"time_offset": "Ekstra tidsforskyvning"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hvor mange dager å reise tilbake i tid. Glidebryter-område: 0 til 374 dager (≈1 år). Anbefalt: ≥2 dager for å unngå konflikter med \"yesterday\"-entiteter.",
|
||||||
|
"time_offset": "Valgfri finjustering: Legg til timer og/eller minutter til dagesforskyvningen. Tiden trekkes automatisk fra (reis lenger tilbake). Merk: Sekunder ignoreres - kun minuttbasert presisjon støttes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"api_error": "Kunne ikke hente hjem fra Tibber API"
|
"no_time_offset": "Minst én tidsforskyvningsverdi må være negativ (kun historiske data)."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_parent_entry": "Overordnet oppføring ikke funnet",
|
"already_configured": "**En tidsreisevisning med denne eksakte tidsforskyvningen eksisterer allerede.**\n\nVelg en annen forskyvning.",
|
||||||
"no_access_token": "Ingen tilgangstoken tilgjengelig",
|
"no_main_entries": "Ingen hovedkonfigurasjonsoppføringer funnet. Legg til et Tibber-hjem først.",
|
||||||
"home_not_found": "Valgt hjem ikke funnet",
|
"parent_entry_not_found": "Valgt konfigurasjonsoppføring ikke funnet."
|
||||||
"api_error": "Kunne ikke hente hjem fra Tibber API",
|
},
|
||||||
"no_available_homes": "Ingen flere hjem tilgjengelig for å legge til. Alle hjem fra din Tibber-konto er allerede lagt til."
|
"time_units": {
|
||||||
|
"day": "{count} dag",
|
||||||
|
"days": "{count} dager",
|
||||||
|
"hour": "{count} time",
|
||||||
|
"hours": "{count} timer",
|
||||||
|
"minute": "{count} minutt",
|
||||||
|
"minutes": "{count} minutter",
|
||||||
|
"ago": "{parts} siden",
|
||||||
|
"now": "nå"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -719,6 +776,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_price": {
|
||||||
|
"name": "Hent prisdata",
|
||||||
|
"description": "Hent prisdata for et spesifikt tidsrom med automatisk ruting. Utviklings- og testtjeneste for price_info_for_range API-funksjonen. Bruker automatisk PRICE_INFO, PRICE_INFO_RANGE eller begge basert på tidsromgrensen.",
|
||||||
|
"fields": {
|
||||||
|
"entry_id": {
|
||||||
|
"name": "Oppførings-ID",
|
||||||
|
"description": "Konfigurasjonsoppførings-IDen for Tibber-integrasjonen."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Starttid",
|
||||||
|
"description": "Start av tidsrommet (inklusiv, tidssonetilpasset)."
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "Sluttid",
|
||||||
|
"description": "Slutt av tidsrommet (eksklusiv, tidssonetilpasset)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get_apexcharts_yaml": {
|
"get_apexcharts_yaml": {
|
||||||
"name": "Hent ApexCharts-kort YAML",
|
"name": "Hent ApexCharts-kort YAML",
|
||||||
"description": "Returnerer en klar-til-kopier YAML-snippet for et ApexCharts-kort som visualiserer Tibber-priser for den valgte dagen. Bruk dette for å enkelt legge til et forhåndskonfigurert diagram til dashboardet ditt. YAML vil bruke get_chartdata-tjenesten for data.",
|
"description": "Returnerer en klar-til-kopier YAML-snippet for et ApexCharts-kort som visualiserer Tibber-priser for den valgte dagen. Bruk dette for å enkelt legge til et forhåndskonfigurert diagram til dashboardet ditt. YAML vil bruke get_chartdata-tjenesten for data.",
|
||||||
|
|
@ -843,6 +918,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
"account_choice": {
|
||||||
|
"options": {
|
||||||
|
"new_token": "Legg til ny Tibber-konto med API-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"options": {
|
"options": {
|
||||||
"yesterday": "I går",
|
"yesterday": "I går",
|
||||||
|
|
@ -917,4 +997,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prisinformasjon & Vurderinger"
|
"title": "Tibber Prisinformasjon & Vurderinger"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
|
"account_choice": {
|
||||||
|
"title": "Kies account",
|
||||||
|
"description": "Je kunt een ander huis van een bestaande Tibber-account toevoegen of een nieuw API-token invoeren voor een ander account.",
|
||||||
|
"data": {
|
||||||
|
"account_choice": "Account"
|
||||||
|
},
|
||||||
|
"submit": "Doorgaan →"
|
||||||
|
},
|
||||||
|
"new_token": {
|
||||||
|
"title": "Voer API-token in",
|
||||||
|
"description": "Stel Tibber Prijsinformatie & Beoordelingen in.\n\nOm een API-toegangstoken te genereren, bezoek https://developer.tibber.com.",
|
||||||
|
"data": {
|
||||||
|
"access_token": "API-toegangstoken"
|
||||||
|
},
|
||||||
|
"submit": "Token valideren"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Stel Tibber Prijsinformatie & Beoordelingen in.\n\nOm een API-toegangstoken te genereren, bezoek https://developer.tibber.com.",
|
"description": "Stel Tibber Prijsinformatie & Beoordelingen in.\n\nOm een API-toegangstoken te genereren, bezoek https://developer.tibber.com.",
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -40,15 +56,24 @@
|
||||||
"cannot_connect": "Verbinding mislukt",
|
"cannot_connect": "Verbinding mislukt",
|
||||||
"invalid_access_token": "Ongeldig toegangstoken",
|
"invalid_access_token": "Ongeldig toegangstoken",
|
||||||
"missing_homes": "Het nieuwe toegangstoken heeft geen toegang tot alle geconfigureerde huizen. Gebruik een toegangstoken dat toegang heeft tot dezelfde Tibber-huizen.",
|
"missing_homes": "Het nieuwe toegangstoken heeft geen toegang tot alle geconfigureerde huizen. Gebruik een toegangstoken dat toegang heeft tot dezelfde Tibber-huizen.",
|
||||||
"invalid_yaml_syntax": "Ongeldige YAML-syntaxis. Controleer inspringen, dubbele punten en speciale tekens.",
|
"home_already_configured": "Dit huis is al geconfigureerd in een ander item. Elk huis kan slechts één keer worden geconfigureerd.",
|
||||||
|
"no_active_subscription": "Dit huis heeft geen actief Tibber-contract. Alleen huizen met actieve elektriciteitscontracten kunnen worden toegevoegd aan Home Assistant.",
|
||||||
|
"subscription_expired": "Het Tibber-contract voor dit huis is verlopen. Alleen huizen met actieve of toekomstige elektriciteitscontracten kunnen worden toegevoegd aan Home Assistant.",
|
||||||
|
"future_subscription_warning": "Let op: Het Tibber-contract voor dit huis is nog niet begonnen. De functionaliteit kan beperkt zijn totdat het contract actief wordt.",
|
||||||
|
"invalid_yaml_syntax": "Ongeldige YAML-syntaxis. Controleer inspringingen, dubbele punten en speciale tekens.",
|
||||||
"invalid_yaml_structure": "YAML moet een woordenboek/object zijn (sleutel: waarde-paren), geen lijst of platte tekst.",
|
"invalid_yaml_structure": "YAML moet een woordenboek/object zijn (sleutel: waarde-paren), geen lijst of platte tekst.",
|
||||||
"service_call_failed": "Service-aanroep validatie mislukt: {error_detail}"
|
"service_call_failed": "Service-aanroep validatie mislukt: {error_detail}",
|
||||||
|
"missing_entry_id": "Entry ID is vereist maar niet opgegeven.",
|
||||||
|
"invalid_entry_id": "Ongeldige entry ID of entry niet gevonden.",
|
||||||
|
"missing_home_id": "Huis-ID ontbreekt in de configuratie.",
|
||||||
|
"user_data_not_available": "Gebruikersgegevens niet beschikbaar. Vernieuw eerst de gebruikersgegevens.",
|
||||||
|
"price_fetch_failed": "Prijsgegevens ophalen mislukt. Controleer de logs voor details."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Integratie is al geconfigureerd",
|
"already_configured": "Alle beschikbare Tibber-huizen zijn al geconfigureerd. Elk huis kan slechts één keer worden geconfigureerd.",
|
||||||
"entry_not_found": "Tibber-configuratie-item niet gevonden.",
|
"entry_not_found": "Tibber-configuratie-item niet gevonden.",
|
||||||
"setup_complete": "Installatie voltooid! Je kunt aanvullende opties voor Tibber-prijzen wijzigen in de integratie-opties na het sluiten van dit dialoogvenster.",
|
"setup_complete": "Installatie voltooid! Je kunt extra opties voor Tibber Prices wijzigen in de integratie-opties na het sluiten van deze dialoog.",
|
||||||
"reauth_successful": "Herauthenticatie succesvol. De integratie is bijgewerkt met het nieuwe toegangstoken."
|
"reauth_successful": "Herauthenticatie geslaagd. De integratie is bijgewerkt met het nieuwe toegangstoken."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
|
@ -57,27 +82,59 @@
|
||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"home": {
|
"home": {
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Tibber-huis toevoegen"
|
"user": "Tijdreisweergave maken"
|
||||||
},
|
},
|
||||||
"title": "Tibber-huis toevoegen",
|
"title": "Tijdreisweergave maken",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Tibber-huis toevoegen",
|
"title": "Configuratie-item selecteren",
|
||||||
"description": "Selecteer een huis om toe te voegen aan je Tibber-integratie.\n\n**Opmerking:** Na het toevoegen van dit huis kun je extra huizen toevoegen via het contextmenu van de integratie door \"Tibber-huis toevoegen\" te selecteren.",
|
"description": "Selecteer het configuratie-item waarvoor je een tijdreisweergave wilt maken.\n\n**Tijdreisweergaves** stellen je in staat om historische prijsgegevens te zien alsof het de huidige tijd is. Dit is handig voor het testen van automatiseringen of het analyseren van eerdere prijspatronen.",
|
||||||
"data": {
|
"data": {
|
||||||
"home_id": "Huis"
|
"parent_entry_id": "Configuratie-item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time_offset": {
|
||||||
|
"title": "Tijdverschuiving configureren",
|
||||||
|
"description": "Configureer hoe ver terug in de tijd deze weergave moet reizen.\n\n**Aanbevolen:** Gebruik **≥2 dagen** verschuiving om conflicten met \"yesterday\"-entiteiten te vermijden die ook historische gegevens bieden.\n\n**Voorbeelden:**\n• **-7 dagen**: Toon prijzen van 7 dagen geleden\n• **-2 dagen, 3 uur**: Toon prijzen van 2 dagen en 3 uur geleden\n• **-14 dagen**: Toon prijzen van 2 weken geleden",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dagen terug",
|
||||||
|
"time_offset": "Extra tijdverschuiving"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hoeveel dagen terug in de tijd reizen. Schuifregelaar bereik: 0 tot 374 dagen (≈1 jaar). Aanbevolen: ≥2 dagen om conflicten met \"yesterday\"-entiteiten te vermijden.",
|
||||||
|
"time_offset": "Optionele fijnafstemming: Voeg uren en/of minuten toe aan de dagverschuiving. De tijd wordt automatisch afgetrokken (verder terug reizen). Let op: Seconden worden genegeerd - alleen precisie op minuutniveau wordt ondersteund."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"title": "Tijdverschuiving opnieuw configureren",
|
||||||
|
"description": "Werk de tijdverschuiving voor deze tijdreisweergave bij.",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dagen terug",
|
||||||
|
"time_offset": "Extra tijdverschuiving"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hoeveel dagen terug in de tijd reizen. Schuifregelaar bereik: 0 tot 374 dagen (≈1 jaar). Aanbevolen: ≥2 dagen om conflicten met \"yesterday\"-entiteiten te vermijden.",
|
||||||
|
"time_offset": "Optionele fijnafstemming: Voeg uren en/of minuten toe aan de dagverschuiving. De tijd wordt automatisch afgetrokken (verder terug reizen). Let op: Seconden worden genegeerd - alleen precisie op minuutniveau wordt ondersteund."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"api_error": "Ophalen van huizen van Tibber API mislukt"
|
"no_time_offset": "Ten minste één tijdverschuivingswaarde moet negatief zijn (alleen historische gegevens)."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_parent_entry": "Bovenliggend item niet gevonden",
|
"already_configured": "**Een tijdreis-weergave met deze exacte tijdverschuiving bestaat al.**\n\nKies een andere verschuiving.",
|
||||||
"no_access_token": "Geen toegangstoken beschikbaar",
|
"no_main_entries": "Geen hoofdconfiguratie-items gevonden. Voeg eerst een Tibber-huis toe.",
|
||||||
"home_not_found": "Geselecteerd huis niet gevonden",
|
"parent_entry_not_found": "Geselecteerd configuratie-item niet gevonden."
|
||||||
"api_error": "Ophalen van huizen van Tibber API mislukt",
|
},
|
||||||
"no_available_homes": "Geen extra huizen beschikbaar om toe te voegen. Alle huizen van je Tibber-account zijn al toegevoegd."
|
"time_units": {
|
||||||
|
"day": "{count} dag",
|
||||||
|
"days": "{count} dagen",
|
||||||
|
"hour": "{count} uur",
|
||||||
|
"hours": "{count} uur",
|
||||||
|
"minute": "{count} minuut",
|
||||||
|
"minutes": "{count} minuten",
|
||||||
|
"ago": "{parts} geleden",
|
||||||
|
"now": "nu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -719,6 +776,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_price": {
|
||||||
|
"name": "Prijsgegevens ophalen",
|
||||||
|
"description": "Haal prijsgegevens op voor een specifiek tijdsbereik met automatische routing. Ontwikkelings- en testservice voor de price_info_for_range API-functie. Gebruikt automatisch PRICE_INFO, PRICE_INFO_RANGE of beide op basis van de tijdsbereikgrens.",
|
||||||
|
"fields": {
|
||||||
|
"entry_id": {
|
||||||
|
"name": "Entry ID",
|
||||||
|
"description": "De configuratie entry ID voor de Tibber integratie."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Starttijd",
|
||||||
|
"description": "Begin van het tijdsbereik (inclusief, tijdzonebewust)."
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "Eindtijd",
|
||||||
|
"description": "Einde van het tijdsbereik (exclusief, tijdzonebewust)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get_apexcharts_yaml": {
|
"get_apexcharts_yaml": {
|
||||||
"name": "ApexCharts-kaart YAML ophalen",
|
"name": "ApexCharts-kaart YAML ophalen",
|
||||||
"description": "Retourneert een kant-en-klaar YAML-fragment voor een ApexCharts-kaart die Tibber-prijzen voor de geselecteerde dag visualiseert. Gebruik dit om eenvoudig een vooraf geconfigureerd diagram aan je dashboard toe te voegen. De YAML gebruikt de get_chartdata-service voor gegevens.",
|
"description": "Retourneert een kant-en-klaar YAML-fragment voor een ApexCharts-kaart die Tibber-prijzen voor de geselecteerde dag visualiseert. Gebruik dit om eenvoudig een vooraf geconfigureerd diagram aan je dashboard toe te voegen. De YAML gebruikt de get_chartdata-service voor gegevens.",
|
||||||
|
|
@ -843,6 +918,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
"account_choice": {
|
||||||
|
"options": {
|
||||||
|
"new_token": "Nieuw Tibber-account toevoegen met API-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"options": {
|
"options": {
|
||||||
"yesterday": "Gisteren",
|
"yesterday": "Gisteren",
|
||||||
|
|
@ -917,4 +997,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prijsinformatie & Beoordelingen"
|
"title": "Tibber Prijsinformatie & Beoordelingen"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
|
"account_choice": {
|
||||||
|
"title": "Välj konto",
|
||||||
|
"description": "Du kan lägga till ett annat hem från ett befintligt Tibber-konto eller ange ett nytt API-token för ett annat konto.",
|
||||||
|
"data": {
|
||||||
|
"account_choice": "Konto"
|
||||||
|
},
|
||||||
|
"submit": "Fortsätt →"
|
||||||
|
},
|
||||||
|
"new_token": {
|
||||||
|
"title": "Ange API-token",
|
||||||
|
"description": "Konfigurera Tibber Prisinformation & Betyg.\n\nFör att generera en API-åtkomsttoken, besök https://developer.tibber.com.",
|
||||||
|
"data": {
|
||||||
|
"access_token": "API-åtkomsttoken"
|
||||||
|
},
|
||||||
|
"submit": "Validera token"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Konfigurera Tibber Prisinformation & Betyg.\n\nFör att generera en API-åtkomsttoken, besök https://developer.tibber.com.",
|
"description": "Konfigurera Tibber Prisinformation & Betyg.\n\nFör att generera en API-åtkomsttoken, besök https://developer.tibber.com.",
|
||||||
"data": {
|
"data": {
|
||||||
|
|
@ -39,15 +55,24 @@
|
||||||
"unknown": "Oväntat fel",
|
"unknown": "Oväntat fel",
|
||||||
"cannot_connect": "Kunde inte ansluta",
|
"cannot_connect": "Kunde inte ansluta",
|
||||||
"invalid_access_token": "Ogiltig åtkomsttoken",
|
"invalid_access_token": "Ogiltig åtkomsttoken",
|
||||||
"missing_homes": "Den nya åtkomsttoken har inte åtkomst till alla konfigurerade hem. Vänligen använd en åtkomsttoken som har åtkomst till samma Tibber-hem.",
|
"missing_homes": "Den nya åtkomsttoken har inte åtkomst till alla konfigurerade hem. Använd en åtkomsttoken som har åtkomst till samma Tibber-hem.",
|
||||||
"invalid_yaml_syntax": "Ogiltig YAML-syntax. Kontrollera indrag, kolon och specialtecken.",
|
"home_already_configured": "Detta hem är redan konfigurerat i en annan post. Varje hem kan endast konfigureras en gång.",
|
||||||
|
"no_active_subscription": "Detta hem har inget aktivt Tibber-avtal. Endast hem med aktiva elavtal kan läggas till i Home Assistant.",
|
||||||
|
"subscription_expired": "Tibber-avtalet för detta hem har gått ut. Endast hem med aktiva eller framtida elavtal kan läggas till i Home Assistant.",
|
||||||
|
"future_subscription_warning": "Obs: Tibber-avtalet för detta hem har inte startat än. Funktionaliteten kan vara begränsad tills avtalet blir aktivt.",
|
||||||
|
"invalid_yaml_syntax": "Ogiltig YAML-syntax. Kontrollera indragning, kolon och specialtecken.",
|
||||||
"invalid_yaml_structure": "YAML måste vara en ordbok/objekt (nyckel: värde-par), inte en lista eller ren text.",
|
"invalid_yaml_structure": "YAML måste vara en ordbok/objekt (nyckel: värde-par), inte en lista eller ren text.",
|
||||||
"service_call_failed": "Service-anrop validering misslyckades: {error_detail}"
|
"service_call_failed": "Tjänsteanropsvalidering misslyckades: {error_detail}",
|
||||||
|
"missing_entry_id": "Post-ID krävs men tillhandahölls inte.",
|
||||||
|
"invalid_entry_id": "Ogiltig post-ID eller post hittades inte.",
|
||||||
|
"missing_home_id": "Hem-ID saknas från konfigurationsposten.",
|
||||||
|
"user_data_not_available": "Användardata är inte tillgänglig. Uppdatera användardata först.",
|
||||||
|
"price_fetch_failed": "Kunde inte hämta prisdata. Kontrollera loggarna för detaljer."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Integrationen är redan konfigurerad",
|
"already_configured": "Alla tillgängliga Tibber-hem är redan konfigurerade. Varje hem kan endast konfigureras en gång.",
|
||||||
"entry_not_found": "Tibber-konfigurationspost hittades inte.",
|
"entry_not_found": "Tibber-konfigurationspost hittades inte.",
|
||||||
"setup_complete": "Konfiguration klar! Du kan ändra ytterligare alternativ för Tibber-priser i integrationens alternativ efter att ha stängt denna dialog.",
|
"setup_complete": "Installation klar! Du kan ändra ytterligare alternativ för Tibber Prices i integrationens alternativ efter att ha stängt denna dialog.",
|
||||||
"reauth_successful": "Omautentisering lyckades. Integrationen har uppdaterats med den nya åtkomsttoken."
|
"reauth_successful": "Omautentisering lyckades. Integrationen har uppdaterats med den nya åtkomsttoken."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -57,27 +82,59 @@
|
||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"home": {
|
"home": {
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Lägg till Tibber-hem"
|
"user": "Skapa tidsresevy"
|
||||||
},
|
},
|
||||||
"title": "Lägg till Tibber-hem",
|
"title": "Skapa tidsresevy",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Lägg till Tibber-hem",
|
"title": "Välj konfigurationspost",
|
||||||
"description": "Välj ett hem att lägga till i din Tibber-integration.\n\n**Obs:** Efter att ha lagt till detta hem kan du lägga till ytterligare hem från integrationens kontextmeny genom att välja \"Lägg till Tibber-hem\".",
|
"description": "Välj konfigurationsposten som du vill skapa en tidsresevy för.\n\n**Tidsresevyer** låter dig se historiska prisdata som om det vore nuvarande tid. Detta är användbart för att testa automationer eller analysera tidigare prismönster.",
|
||||||
"data": {
|
"data": {
|
||||||
"home_id": "Hem"
|
"parent_entry_id": "Konfigurationspost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time_offset": {
|
||||||
|
"title": "Konfigurera tidsförskjutning",
|
||||||
|
"description": "Konfigurera hur långt tillbaka i tiden denna vy ska resa.\n\n**Rekommenderat:** Använd **≥2 dagar** förskjutning för att undvika konflikter med \"yesterday\"-entiteter som också tillhandahåller historisk data.\n\n**Exempel:**\n• **-7 dagar**: Visa priser från 7 dagar sedan\n• **-2 dagar, 3 timmar**: Visa priser från 2 dagar och 3 timmar sedan\n• **-14 dagar**: Visa priser från 2 veckor sedan",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dagar tillbaka",
|
||||||
|
"time_offset": "Extra tidsförskjutning"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hur många dagar att resa tillbaka i tiden. Skjutreglage område: 0 till 374 dagar (≈1 år). Rekommenderat: ≥2 dagar för att undvika konflikter med \"yesterday\"-entiteter.",
|
||||||
|
"time_offset": "Valfri finjustering: Lägg till timmar och/eller minuter till dagförskjutningen. Tiden subtraheras automatiskt (res längre tillbaka). Obs: Sekunder ignoreras - endast minutbaserad precision stöds."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"title": "Konfigurera om tidsförskjutning",
|
||||||
|
"description": "Uppdatera tidsförskjutningen för denna tidsresevy.",
|
||||||
|
"data": {
|
||||||
|
"virtual_time_offset_days": "Dagar tillbaka",
|
||||||
|
"time_offset": "Extra tidsförskjutning"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"virtual_time_offset_days": "Hur många dagar att resa tillbaka i tiden. Skjutreglage område: 0 till 374 dagar (≈1 år). Rekommenderat: ≥2 dagar för att undvika konflikter med \"yesterday\"-entiteter.",
|
||||||
|
"time_offset": "Valfri finjustering: Lägg till timmar och/eller minuter till dagförskjutningen. Tiden subtraheras automatiskt (res längre tillbaka). Obs: Sekunder ignoreras - endast minutbaserad precision stöds."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"api_error": "Kunde inte hämta hem från Tibber API"
|
"no_time_offset": "Minst ett tidsförskjutningsvärde måste vara negativt (endast historiska data)."
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_parent_entry": "Överordnad post hittades inte",
|
"already_configured": "**En tidsresevy med denna exakta tidsförskjutning existerar redan.**\n\nVälj en annan förskjutning.",
|
||||||
"no_access_token": "Ingen åtkomsttoken tillgänglig",
|
"no_main_entries": "Inga huvudkonfigurationsposter hittades. Lägg först till ett Tibber-hem.",
|
||||||
"home_not_found": "Valt hem hittades inte",
|
"parent_entry_not_found": "Vald konfigurationspost hittades inte."
|
||||||
"api_error": "Kunde inte hämta hem från Tibber API",
|
},
|
||||||
"no_available_homes": "Inga ytterligare hem tillgängliga att lägga till. Alla hem från ditt Tibber-konto har redan lagts till."
|
"time_units": {
|
||||||
|
"day": "{count} dag",
|
||||||
|
"days": "{count} dagar",
|
||||||
|
"hour": "{count} timme",
|
||||||
|
"hours": "{count} timmar",
|
||||||
|
"minute": "{count} minut",
|
||||||
|
"minutes": "{count} minuter",
|
||||||
|
"ago": "{parts} sedan",
|
||||||
|
"now": "nu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -719,6 +776,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_price": {
|
||||||
|
"name": "Hämta prisdata",
|
||||||
|
"description": "Hämta prisdata för ett specifikt tidsintervall med automatisk routing. Utvecklings- och testtjänst för price_info_for_range API-funktionen. Använder automatiskt PRICE_INFO, PRICE_INFO_RANGE eller båda baserat på tidsintervallgränsen.",
|
||||||
|
"fields": {
|
||||||
|
"entry_id": {
|
||||||
|
"name": "Post-ID",
|
||||||
|
"description": "Konfigurationspost-ID för Tibber-integrationen."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Starttid",
|
||||||
|
"description": "Start av tidsintervallet (inklusiv, tidszonskänslig)."
|
||||||
|
},
|
||||||
|
"end_time": {
|
||||||
|
"name": "Sluttid",
|
||||||
|
"description": "Slut av tidsintervallet (exklusiv, tidszonskänslig)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"get_apexcharts_yaml": {
|
"get_apexcharts_yaml": {
|
||||||
"name": "Hämta ApexCharts-kort YAML",
|
"name": "Hämta ApexCharts-kort YAML",
|
||||||
"description": "Returnerar ett färdigt YAML-utklipp för ett ApexCharts-kort som visualiserar Tibber-priser för den valda dagen. Använd detta för att enkelt lägga till ett förkonfigurerat diagram till din instrumentpanel. YAML kommer att använda get_chartdata-tjänsten för data.",
|
"description": "Returnerar ett färdigt YAML-utklipp för ett ApexCharts-kort som visualiserar Tibber-priser för den valda dagen. Använd detta för att enkelt lägga till ett förkonfigurerat diagram till din instrumentpanel. YAML kommer att använda get_chartdata-tjänsten för data.",
|
||||||
|
|
@ -843,6 +918,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
"account_choice": {
|
||||||
|
"options": {
|
||||||
|
"new_token": "Lägg till nytt Tibber-konto med API-token"
|
||||||
|
}
|
||||||
|
},
|
||||||
"day": {
|
"day": {
|
||||||
"options": {
|
"options": {
|
||||||
"yesterday": "Igår",
|
"yesterday": "Igår",
|
||||||
|
|
@ -917,4 +997,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Tibber Prisinformation & Betyg"
|
"title": "Tibber Prisinformation & Betyg"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue