diff --git a/custom_components/tibber_prices/__init__.py b/custom_components/tibber_prices/__init__.py index e2be89e..fe33981 100644 --- a/custom_components/tibber_prices/__init__.py +++ b/custom_components/tibber_prices/__init__.py @@ -126,14 +126,15 @@ async def _migrate_config_options(hass: HomeAssistant, entry: ConfigEntry) -> No migration_performed = False migrated = dict(entry.options) - # Migration: Set currency_display_mode to minor for existing configs - # New configs get currency-appropriate defaults from schema. - # This preserves legacy behavior where all prices were in subunit currency. + # Migration: Set currency_display_mode to subunit for legacy configs + # New configs (created after v1.1.0) get currency-appropriate defaults via get_default_options(). + # This migration preserves legacy behavior where all prices were in subunit currency (cents/øre). + # Only runs for old config entries that don't have this option explicitly set. if CONF_CURRENCY_DISPLAY_MODE not in migrated: migrated[CONF_CURRENCY_DISPLAY_MODE] = DISPLAY_MODE_SUBUNIT migration_performed = True LOGGER.info( - "[%s] Migrated config: Set currency_display_mode=%s (legacy default for existing configs)", + "[%s] Migrated legacy config: Set currency_display_mode=%s (preserves pre-v1.1.0 behavior)", entry.title, DISPLAY_MODE_SUBUNIT, ) @@ -276,7 +277,8 @@ async def async_setup_entry( # https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities if entry.state == ConfigEntryState.SETUP_IN_PROGRESS: await coordinator.async_config_entry_first_refresh() - entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + # Note: Options update listener is registered in coordinator.__init__ + # (handles cache invalidation + refresh without full reload) else: await coordinator.async_refresh() diff --git a/custom_components/tibber_prices/config_flow_handlers/options_flow.py b/custom_components/tibber_prices/config_flow_handlers/options_flow.py index 6d75faf..5ea073a 100644 --- a/custom_components/tibber_prices/config_flow_handlers/options_flow.py +++ b/custom_components/tibber_prices/config_flow_handlers/options_flow.py @@ -3,7 +3,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any, ClassVar +from copy import deepcopy +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from collections.abc import Mapping @@ -16,6 +17,7 @@ from custom_components.tibber_prices.config_flow_handlers.schemas import ( get_peak_price_schema, get_price_rating_schema, get_price_trend_schema, + get_reset_to_defaults_schema, get_volatility_schema, ) from custom_components.tibber_prices.config_flow_handlers.validators import ( @@ -60,6 +62,7 @@ from custom_components.tibber_prices.const import ( DEFAULT_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH, DOMAIN, + get_default_options, ) from homeassistant.config_entries import ConfigFlowResult, OptionsFlow @@ -69,23 +72,34 @@ _LOGGER = logging.getLogger(__name__) class TibberPricesOptionsFlowHandler(OptionsFlow): """Handle options for tibber_prices entries.""" - # Step progress tracking - _TOTAL_STEPS: ClassVar[int] = 8 - _STEP_INFO: ClassVar[dict[str, int]] = { - "init": 1, - "display_settings": 2, - "current_interval_price_rating": 3, - "volatility": 4, - "best_price": 5, - "peak_price": 6, - "price_trend": 7, - "chart_data_export": 8, - } - def __init__(self) -> None: """Initialize options flow.""" self._options: dict[str, Any] = {} + def _merge_section_data(self, user_input: dict[str, Any]) -> None: + """ + Merge section data from form input into options. + + Home Assistant forms with section() return nested dicts like: + {"section_name": {"setting1": value1, "setting2": value2}} + + We need to preserve this structure in config_entry.options. + + Args: + user_input: Nested user input from form with sections + + """ + for section_key, section_data in user_input.items(): + if isinstance(section_data, dict): + # This is a section - ensure the section exists in options + if section_key not in self._options: + self._options[section_key] = {} + # Update the section with new values + self._options[section_key].update(section_data) + else: + # This is a direct value - keep it as is + self._options[section_key] = section_data + def _migrate_config_options(self, options: Mapping[str, Any]) -> dict[str, Any]: """ Migrate deprecated config options to current format. @@ -100,7 +114,10 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): Migrated options dict with deprecated keys removed/renamed """ - migrated = dict(options) + # CRITICAL: Use deepcopy to avoid modifying the original config_entry.options + # If we use dict(options), nested dicts are still referenced, causing + # self._options modifications to leak into config_entry.options + migrated = deepcopy(dict(options)) migration_performed = False # Migration 1: Rename relaxation_step_* to relaxation_attempts_* @@ -144,41 +161,98 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): return migrated - def _get_step_description_placeholders(self, step_id: str) -> dict[str, str]: - """Get description placeholders with step progress.""" - if step_id not in self._STEP_INFO: - return {} + def _save_options_if_changed(self) -> bool: + """ + Save options only if they actually changed. - step_num = self._STEP_INFO[step_id] + Returns: + True if options were updated, False if no changes detected - # 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, {}) + """ + # Compare old and new options + if self.config_entry.options != self._options: + self.hass.config_entries.async_update_entry( + self.config_entry, + options=self._options, + ) + return True + return False - # Get step progress text from translations with placeholders - step_progress_template = translations.get("common", {}).get("step_progress", "Step {step_num} of {total_steps}") - step_progress = step_progress_template.format(step_num=step_num, total_steps=self._TOTAL_STEPS) + async def async_step_init(self, _user_input: dict[str, Any] | None = None) -> ConfigFlowResult: + """Manage the options - show menu.""" + # Always reload options from config_entry to get latest saved state + # This ensures changes from previous steps are visible + self._options = self._migrate_config_options(self.config_entry.options) - return { - "step_progress": step_progress, - } - - async def async_step_init(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: - """Manage the options - General Settings.""" - # Initialize options from config_entry on first call - if not self._options: - # Migrate deprecated config options before processing - self._options = self._migrate_config_options(self.config_entry.options) + # Show menu with all configuration categories + return self.async_show_menu( + step_id="init", + menu_options=[ + "general_settings", + "display_settings", + "current_interval_price_rating", + "volatility", + "best_price", + "peak_price", + "price_trend", + "chart_data_export", + "reset_to_defaults", + "finish", + ], + ) + async def async_step_reset_to_defaults(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: + """Reset all settings to factory defaults.""" if user_input is not None: + # Check if user confirmed the reset + if user_input.get("confirm_reset", False): + # Get currency from config_entry.data (this is immutable and safe) + currency_code = self.config_entry.data.get("currency", None) + + # Completely replace options with fresh defaults (factory reset) + # This discards ALL old data including legacy structures + self._options = get_default_options(currency_code) + + # Force save the new options + self._save_options_if_changed() + + _LOGGER.info( + "Factory reset performed for config entry '%s' - all settings restored to defaults", + self.config_entry.title, + ) + + # Show success message and return to menu + return self.async_abort(reason="reset_successful") + + # User didn't check the box - they want to cancel + # Show info message (not error) and return to menu + return self.async_abort(reason="reset_cancelled") + + # Show confirmation form with checkbox + return self.async_show_form( + step_id="reset_to_defaults", + data_schema=get_reset_to_defaults_schema(), + ) + + async def async_step_finish(self, _user_input: dict[str, Any] | None = None) -> ConfigFlowResult: + """Close the options flow.""" + # Use empty reason to close without any message + return self.async_abort(reason="finished") + + async def async_step_general_settings(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: + """Configure general settings.""" + if user_input is not None: + # Update options with new values self._options.update(user_input) - return await self.async_step_display_settings() + # Save options only if changed (triggers listeners automatically) + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( - step_id="init", + step_id="general_settings", data_schema=get_options_init_schema(self.config_entry.options), description_placeholders={ - **self._get_step_description_placeholders("init"), "user_login": self.config_entry.data.get("user_login", "N/A"), }, ) @@ -194,13 +268,16 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): currency_code = tibber_data.coordinator.data.get("currency") if user_input is not None: + # Update options with new values self._options.update(user_input) - return await self.async_step_current_interval_price_rating() + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="display_settings", data_schema=get_display_settings_schema(self.config_entry.options, currency_code), - description_placeholders=self._get_step_description_placeholders("display_settings"), ) async def async_step_current_interval_price_rating( @@ -210,6 +287,9 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors: dict[str, str] = {} if user_input is not None: + # Schema is now flattened - fields come directly in user_input + # But we still need to store them in nested structure for coordinator + # Validate low price rating threshold if CONF_PRICE_RATING_THRESHOLD_LOW in user_input and not validate_price_rating_threshold_low( user_input[CONF_PRICE_RATING_THRESHOLD_LOW] @@ -223,25 +303,29 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors[CONF_PRICE_RATING_THRESHOLD_HIGH] = "invalid_price_rating_high" # Cross-validate both thresholds together (LOW must be < HIGH) - if not errors and not validate_price_rating_thresholds( - user_input.get( + if not errors: + # Get current values directly from options (now flat) + low_val = user_input.get( CONF_PRICE_RATING_THRESHOLD_LOW, self._options.get(CONF_PRICE_RATING_THRESHOLD_LOW, -10) - ), - user_input.get( + ) + high_val = user_input.get( CONF_PRICE_RATING_THRESHOLD_HIGH, self._options.get(CONF_PRICE_RATING_THRESHOLD_HIGH, 10) - ), - ): - # This should never happen given the range constraints, but add error for safety - errors["base"] = "invalid_price_rating_thresholds" + ) + if not validate_price_rating_thresholds(low_val, high_val): + # This should never happen given the range constraints, but add error for safety + errors["base"] = "invalid_price_rating_thresholds" if not errors: + # Store flat data directly in options (no section wrapping) self._options.update(user_input) - return await self.async_step_volatility() + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="current_interval_price_rating", data_schema=get_price_rating_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("current_interval_price_rating"), errors=errors, ) @@ -250,46 +334,61 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors: dict[str, str] = {} if user_input is not None: + # Extract settings from sections + period_settings = user_input.get("period_settings", {}) + flexibility_settings = user_input.get("flexibility_settings", {}) + relaxation_settings = user_input.get("relaxation_and_target_periods", {}) + # Validate period length - if CONF_BEST_PRICE_MIN_PERIOD_LENGTH in user_input and not validate_period_length( - user_input[CONF_BEST_PRICE_MIN_PERIOD_LENGTH] + if CONF_BEST_PRICE_MIN_PERIOD_LENGTH in period_settings and not validate_period_length( + period_settings[CONF_BEST_PRICE_MIN_PERIOD_LENGTH] ): errors[CONF_BEST_PRICE_MIN_PERIOD_LENGTH] = "invalid_period_length" # Validate flex percentage - if CONF_BEST_PRICE_FLEX in user_input and not validate_flex_percentage(user_input[CONF_BEST_PRICE_FLEX]): + if CONF_BEST_PRICE_FLEX in flexibility_settings and not validate_flex_percentage( + flexibility_settings[CONF_BEST_PRICE_FLEX] + ): errors[CONF_BEST_PRICE_FLEX] = "invalid_flex" # Validate distance from average (Best Price uses negative values) - if CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG in user_input and not validate_best_price_distance_percentage( - user_input[CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG] + if ( + CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG in flexibility_settings + and not validate_best_price_distance_percentage( + flexibility_settings[CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG] + ) ): errors[CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG] = "invalid_best_price_distance" # Validate minimum periods count - if CONF_MIN_PERIODS_BEST in user_input and not validate_min_periods(user_input[CONF_MIN_PERIODS_BEST]): + if CONF_MIN_PERIODS_BEST in relaxation_settings and not validate_min_periods( + relaxation_settings[CONF_MIN_PERIODS_BEST] + ): errors[CONF_MIN_PERIODS_BEST] = "invalid_min_periods" # Validate gap count - if CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT in user_input and not validate_gap_count( - user_input[CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT] + if CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT in period_settings and not validate_gap_count( + period_settings[CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT] ): errors[CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT] = "invalid_gap_count" # Validate relaxation attempts - if CONF_RELAXATION_ATTEMPTS_BEST in user_input and not validate_relaxation_attempts( - user_input[CONF_RELAXATION_ATTEMPTS_BEST] + if CONF_RELAXATION_ATTEMPTS_BEST in relaxation_settings and not validate_relaxation_attempts( + relaxation_settings[CONF_RELAXATION_ATTEMPTS_BEST] ): errors[CONF_RELAXATION_ATTEMPTS_BEST] = "invalid_relaxation_attempts" if not errors: - self._options.update(user_input) - return await self.async_step_peak_price() + # Merge section data into options + self._merge_section_data(user_input) + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="best_price", data_schema=get_best_price_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("best_price"), errors=errors, ) @@ -298,46 +397,58 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors: dict[str, str] = {} if user_input is not None: + # Extract settings from sections + period_settings = user_input.get("period_settings", {}) + flexibility_settings = user_input.get("flexibility_settings", {}) + relaxation_settings = user_input.get("relaxation_and_target_periods", {}) + # Validate period length - if CONF_PEAK_PRICE_MIN_PERIOD_LENGTH in user_input and not validate_period_length( - user_input[CONF_PEAK_PRICE_MIN_PERIOD_LENGTH] + if CONF_PEAK_PRICE_MIN_PERIOD_LENGTH in period_settings and not validate_period_length( + period_settings[CONF_PEAK_PRICE_MIN_PERIOD_LENGTH] ): errors[CONF_PEAK_PRICE_MIN_PERIOD_LENGTH] = "invalid_period_length" # Validate flex percentage (peak uses negative values) - if CONF_PEAK_PRICE_FLEX in user_input and not validate_flex_percentage(user_input[CONF_PEAK_PRICE_FLEX]): + if CONF_PEAK_PRICE_FLEX in flexibility_settings and not validate_flex_percentage( + flexibility_settings[CONF_PEAK_PRICE_FLEX] + ): errors[CONF_PEAK_PRICE_FLEX] = "invalid_flex" # Validate distance from average (Peak Price uses positive values) - if CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG in user_input and not validate_distance_percentage( - user_input[CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG] + if CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG in flexibility_settings and not validate_distance_percentage( + flexibility_settings[CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG] ): errors[CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG] = "invalid_peak_price_distance" # Validate minimum periods count - if CONF_MIN_PERIODS_PEAK in user_input and not validate_min_periods(user_input[CONF_MIN_PERIODS_PEAK]): + if CONF_MIN_PERIODS_PEAK in relaxation_settings and not validate_min_periods( + relaxation_settings[CONF_MIN_PERIODS_PEAK] + ): errors[CONF_MIN_PERIODS_PEAK] = "invalid_min_periods" # Validate gap count - if CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT in user_input and not validate_gap_count( - user_input[CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT] + if CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT in period_settings and not validate_gap_count( + period_settings[CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT] ): errors[CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT] = "invalid_gap_count" # Validate relaxation attempts - if CONF_RELAXATION_ATTEMPTS_PEAK in user_input and not validate_relaxation_attempts( - user_input[CONF_RELAXATION_ATTEMPTS_PEAK] + if CONF_RELAXATION_ATTEMPTS_PEAK in relaxation_settings and not validate_relaxation_attempts( + relaxation_settings[CONF_RELAXATION_ATTEMPTS_PEAK] ): errors[CONF_RELAXATION_ATTEMPTS_PEAK] = "invalid_relaxation_attempts" if not errors: - self._options.update(user_input) - return await self.async_step_price_trend() + # Merge section data into options + self._merge_section_data(user_input) + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="peak_price", data_schema=get_peak_price_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("peak_price"), errors=errors, ) @@ -346,6 +457,9 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors: dict[str, str] = {} if user_input is not None: + # Schema is now flattened - fields come directly in user_input + # Store them flat in options (no nested structure) + # Validate rising trend threshold if CONF_PRICE_TREND_THRESHOLD_RISING in user_input and not validate_price_trend_rising( user_input[CONF_PRICE_TREND_THRESHOLD_RISING] @@ -359,27 +473,29 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors[CONF_PRICE_TREND_THRESHOLD_FALLING] = "invalid_price_trend_falling" if not errors: + # Store flat data directly in options (no section wrapping) self._options.update(user_input) - return await self.async_step_chart_data_export() + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="price_trend", data_schema=get_price_trend_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("price_trend"), errors=errors, ) async def async_step_chart_data_export(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: """Info page for chart data export sensor.""" if user_input is not None: - # No validation needed - just an info page - return self.async_create_entry(title="", data=self._options) + # No changes to save - just return to menu + return await self.async_step_init() # Show info-only form (no input fields) return self.async_show_form( step_id="chart_data_export", data_schema=get_chart_data_export_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("chart_data_export"), ) async def async_step_volatility(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: @@ -387,6 +503,8 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): errors: dict[str, str] = {} if user_input is not None: + # Schema is now flattened - fields come directly in user_input + # Validate moderate volatility threshold if CONF_VOLATILITY_THRESHOLD_MODERATE in user_input and not validate_volatility_threshold_moderate( user_input[CONF_VOLATILITY_THRESHOLD_MODERATE] @@ -407,30 +525,33 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): # Cross-validation: Ensure MODERATE < HIGH < VERY_HIGH if not errors: - existing_options = self.config_entry.options + # Get current values directly from options (now flat) moderate = user_input.get( CONF_VOLATILITY_THRESHOLD_MODERATE, - existing_options.get(CONF_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_MODERATE), + self._options.get(CONF_VOLATILITY_THRESHOLD_MODERATE, DEFAULT_VOLATILITY_THRESHOLD_MODERATE), ) high = user_input.get( CONF_VOLATILITY_THRESHOLD_HIGH, - existing_options.get(CONF_VOLATILITY_THRESHOLD_HIGH, DEFAULT_VOLATILITY_THRESHOLD_HIGH), + self._options.get(CONF_VOLATILITY_THRESHOLD_HIGH, DEFAULT_VOLATILITY_THRESHOLD_HIGH), ) very_high = user_input.get( CONF_VOLATILITY_THRESHOLD_VERY_HIGH, - existing_options.get(CONF_VOLATILITY_THRESHOLD_VERY_HIGH, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH), + self._options.get(CONF_VOLATILITY_THRESHOLD_VERY_HIGH, DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH), ) if not validate_volatility_thresholds(moderate, high, very_high): errors["base"] = "invalid_volatility_thresholds" if not errors: + # Store flat data directly in options (no section wrapping) self._options.update(user_input) - return await self.async_step_best_price() + # async_create_entry automatically handles change detection and listener triggering + self._save_options_if_changed() + # Return to menu for more changes + return await self.async_step_init() return self.async_show_form( step_id="volatility", data_schema=get_volatility_schema(self.config_entry.options), - description_placeholders=self._get_step_description_placeholders("volatility"), errors=errors, ) diff --git a/custom_components/tibber_prices/config_flow_handlers/schemas.py b/custom_components/tibber_prices/config_flow_handlers/schemas.py index f2acda3..63ee42d 100644 --- a/custom_components/tibber_prices/config_flow_handlers/schemas.py +++ b/custom_components/tibber_prices/config_flow_handlers/schemas.py @@ -96,6 +96,7 @@ from custom_components.tibber_prices.const import ( ) from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.data_entry_flow import section +from homeassistant.helpers import selector from homeassistant.helpers.selector import ( BooleanSelector, NumberSelector, @@ -259,112 +260,98 @@ def get_price_rating_schema(options: Mapping[str, Any]) -> vol.Schema: """Return schema for price rating thresholds configuration.""" return vol.Schema( { - vol.Required("price_rating_thresholds"): section( - vol.Schema( - { - vol.Optional( - CONF_PRICE_RATING_THRESHOLD_LOW, - default=int( - options.get( - CONF_PRICE_RATING_THRESHOLD_LOW, - DEFAULT_PRICE_RATING_THRESHOLD_LOW, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_PRICE_RATING_THRESHOLD_LOW, - max=MAX_PRICE_RATING_THRESHOLD_LOW, - unit_of_measurement="%", - step=1, - mode=NumberSelectorMode.SLIDER, - ), - ), - vol.Optional( - CONF_PRICE_RATING_THRESHOLD_HIGH, - default=int( - options.get( - CONF_PRICE_RATING_THRESHOLD_HIGH, - DEFAULT_PRICE_RATING_THRESHOLD_HIGH, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_PRICE_RATING_THRESHOLD_HIGH, - max=MAX_PRICE_RATING_THRESHOLD_HIGH, - unit_of_measurement="%", - step=1, - mode=NumberSelectorMode.SLIDER, - ), - ), - } + vol.Optional( + CONF_PRICE_RATING_THRESHOLD_LOW, + default=int( + options.get( + CONF_PRICE_RATING_THRESHOLD_LOW, + DEFAULT_PRICE_RATING_THRESHOLD_LOW, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_PRICE_RATING_THRESHOLD_LOW, + max=MAX_PRICE_RATING_THRESHOLD_LOW, + unit_of_measurement="%", + step=1, + mode=NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_PRICE_RATING_THRESHOLD_HIGH, + default=int( + options.get( + CONF_PRICE_RATING_THRESHOLD_HIGH, + DEFAULT_PRICE_RATING_THRESHOLD_HIGH, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_PRICE_RATING_THRESHOLD_HIGH, + max=MAX_PRICE_RATING_THRESHOLD_HIGH, + unit_of_measurement="%", + step=1, + mode=NumberSelectorMode.SLIDER, ), - {"collapsed": True}, ), } ) def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema: - """Return schema for volatility thresholds configuration with collapsible sections.""" + """Return schema for volatility thresholds configuration.""" return vol.Schema( { - vol.Required("volatility_thresholds"): section( - vol.Schema( - { - vol.Optional( - CONF_VOLATILITY_THRESHOLD_MODERATE, - default=float( - options.get( - CONF_VOLATILITY_THRESHOLD_MODERATE, - DEFAULT_VOLATILITY_THRESHOLD_MODERATE, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_VOLATILITY_THRESHOLD_MODERATE, - max=MAX_VOLATILITY_THRESHOLD_MODERATE, - step=1.0, - unit_of_measurement="%", - mode=NumberSelectorMode.SLIDER, - ), - ), - vol.Optional( - CONF_VOLATILITY_THRESHOLD_HIGH, - default=float( - options.get( - CONF_VOLATILITY_THRESHOLD_HIGH, - DEFAULT_VOLATILITY_THRESHOLD_HIGH, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_VOLATILITY_THRESHOLD_HIGH, - max=MAX_VOLATILITY_THRESHOLD_HIGH, - step=1.0, - unit_of_measurement="%", - mode=NumberSelectorMode.SLIDER, - ), - ), - vol.Optional( - CONF_VOLATILITY_THRESHOLD_VERY_HIGH, - default=float( - options.get( - CONF_VOLATILITY_THRESHOLD_VERY_HIGH, - DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_VOLATILITY_THRESHOLD_VERY_HIGH, - max=MAX_VOLATILITY_THRESHOLD_VERY_HIGH, - step=1.0, - unit_of_measurement="%", - mode=NumberSelectorMode.SLIDER, - ), - ), - } + vol.Optional( + CONF_VOLATILITY_THRESHOLD_MODERATE, + default=float( + options.get( + CONF_VOLATILITY_THRESHOLD_MODERATE, + DEFAULT_VOLATILITY_THRESHOLD_MODERATE, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_VOLATILITY_THRESHOLD_MODERATE, + max=MAX_VOLATILITY_THRESHOLD_MODERATE, + step=1.0, + unit_of_measurement="%", + mode=NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_VOLATILITY_THRESHOLD_HIGH, + default=float( + options.get( + CONF_VOLATILITY_THRESHOLD_HIGH, + DEFAULT_VOLATILITY_THRESHOLD_HIGH, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_VOLATILITY_THRESHOLD_HIGH, + max=MAX_VOLATILITY_THRESHOLD_HIGH, + step=1.0, + unit_of_measurement="%", + mode=NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_VOLATILITY_THRESHOLD_VERY_HIGH, + default=float( + options.get( + CONF_VOLATILITY_THRESHOLD_VERY_HIGH, + DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_VOLATILITY_THRESHOLD_VERY_HIGH, + max=MAX_VOLATILITY_THRESHOLD_VERY_HIGH, + step=1.0, + unit_of_measurement="%", + mode=NumberSelectorMode.SLIDER, ), - {"collapsed": True}, ), } ) @@ -372,6 +359,7 @@ def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema: def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: """Return schema for best price period configuration with collapsible sections.""" + period_settings = options.get("period_settings", {}) return vol.Schema( { vol.Required("period_settings"): section( @@ -380,7 +368,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_BEST_PRICE_MIN_PERIOD_LENGTH, default=int( - options.get( + period_settings.get( CONF_BEST_PRICE_MIN_PERIOD_LENGTH, DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, ) @@ -396,7 +384,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: ), vol.Optional( CONF_BEST_PRICE_MAX_LEVEL, - default=options.get( + default=period_settings.get( CONF_BEST_PRICE_MAX_LEVEL, DEFAULT_BEST_PRICE_MAX_LEVEL, ), @@ -410,7 +398,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, default=int( - options.get( + period_settings.get( CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT, ) @@ -425,7 +413,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: ), } ), - {"collapsed": True}, + {"collapsed": False}, ), vol.Required("flexibility_settings"): section( vol.Schema( @@ -433,7 +421,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_BEST_PRICE_FLEX, default=int( - options.get( + options.get("flexibility_settings", {}).get( CONF_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX, ) @@ -450,7 +438,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, default=int( - options.get( + options.get("flexibility_settings", {}).get( CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG, ) @@ -473,7 +461,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: { vol.Optional( CONF_ENABLE_MIN_PERIODS_BEST, - default=options.get( + default=options.get("relaxation_and_target_periods", {}).get( CONF_ENABLE_MIN_PERIODS_BEST, DEFAULT_ENABLE_MIN_PERIODS_BEST, ), @@ -481,7 +469,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_MIN_PERIODS_BEST, default=int( - options.get( + options.get("relaxation_and_target_periods", {}).get( CONF_MIN_PERIODS_BEST, DEFAULT_MIN_PERIODS_BEST, ) @@ -497,7 +485,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_RELAXATION_ATTEMPTS_BEST, default=int( - options.get( + options.get("relaxation_and_target_periods", {}).get( CONF_RELAXATION_ATTEMPTS_BEST, DEFAULT_RELAXATION_ATTEMPTS_BEST, ) @@ -520,6 +508,7 @@ def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema: def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: """Return schema for peak price period configuration with collapsible sections.""" + period_settings = options.get("period_settings", {}) return vol.Schema( { vol.Required("period_settings"): section( @@ -528,7 +517,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, default=int( - options.get( + period_settings.get( CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, ) @@ -544,7 +533,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: ), vol.Optional( CONF_PEAK_PRICE_MIN_LEVEL, - default=options.get( + default=period_settings.get( CONF_PEAK_PRICE_MIN_LEVEL, DEFAULT_PEAK_PRICE_MIN_LEVEL, ), @@ -558,7 +547,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, default=int( - options.get( + period_settings.get( CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, ) @@ -573,7 +562,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: ), } ), - {"collapsed": True}, + {"collapsed": False}, ), vol.Required("flexibility_settings"): section( vol.Schema( @@ -581,7 +570,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_PEAK_PRICE_FLEX, default=int( - options.get( + options.get("flexibility_settings", {}).get( CONF_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX, ) @@ -598,7 +587,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, default=int( - options.get( + options.get("flexibility_settings", {}).get( CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, ) @@ -621,7 +610,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: { vol.Optional( CONF_ENABLE_MIN_PERIODS_PEAK, - default=options.get( + default=options.get("relaxation_and_target_periods", {}).get( CONF_ENABLE_MIN_PERIODS_PEAK, DEFAULT_ENABLE_MIN_PERIODS_PEAK, ), @@ -629,7 +618,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_MIN_PERIODS_PEAK, default=int( - options.get( + options.get("relaxation_and_target_periods", {}).get( CONF_MIN_PERIODS_PEAK, DEFAULT_MIN_PERIODS_PEAK, ) @@ -645,7 +634,7 @@ def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema: vol.Optional( CONF_RELAXATION_ATTEMPTS_PEAK, default=int( - options.get( + options.get("relaxation_and_target_periods", {}).get( CONF_RELAXATION_ATTEMPTS_PEAK, DEFAULT_RELAXATION_ATTEMPTS_PEAK, ) @@ -670,46 +659,39 @@ def get_price_trend_schema(options: Mapping[str, Any]) -> vol.Schema: """Return schema for price trend thresholds configuration.""" return vol.Schema( { - vol.Required("price_trend_thresholds"): section( - vol.Schema( - { - vol.Optional( - CONF_PRICE_TREND_THRESHOLD_RISING, - default=int( - options.get( - CONF_PRICE_TREND_THRESHOLD_RISING, - DEFAULT_PRICE_TREND_THRESHOLD_RISING, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_PRICE_TREND_RISING, - max=MAX_PRICE_TREND_RISING, - step=1, - unit_of_measurement="%", - mode=NumberSelectorMode.SLIDER, - ), - ), - vol.Optional( - CONF_PRICE_TREND_THRESHOLD_FALLING, - default=int( - options.get( - CONF_PRICE_TREND_THRESHOLD_FALLING, - DEFAULT_PRICE_TREND_THRESHOLD_FALLING, - ) - ), - ): NumberSelector( - NumberSelectorConfig( - min=MIN_PRICE_TREND_FALLING, - max=MAX_PRICE_TREND_FALLING, - step=1, - unit_of_measurement="%", - mode=NumberSelectorMode.SLIDER, - ), - ), - } + vol.Optional( + CONF_PRICE_TREND_THRESHOLD_RISING, + default=int( + options.get( + CONF_PRICE_TREND_THRESHOLD_RISING, + DEFAULT_PRICE_TREND_THRESHOLD_RISING, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_PRICE_TREND_RISING, + max=MAX_PRICE_TREND_RISING, + step=1, + unit_of_measurement="%", + mode=NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_PRICE_TREND_THRESHOLD_FALLING, + default=int( + options.get( + CONF_PRICE_TREND_THRESHOLD_FALLING, + DEFAULT_PRICE_TREND_THRESHOLD_FALLING, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=MIN_PRICE_TREND_FALLING, + max=MAX_PRICE_TREND_FALLING, + step=1, + unit_of_measurement="%", + mode=NumberSelectorMode.SLIDER, ), - {"collapsed": True}, ), } ) @@ -719,3 +701,12 @@ def get_chart_data_export_schema(_options: Mapping[str, Any]) -> vol.Schema: """Return schema for chart data export info page (no input fields).""" # Empty schema - this is just an info page now return vol.Schema({}) + + +def get_reset_to_defaults_schema() -> vol.Schema: + """Return schema for reset to defaults confirmation step.""" + return vol.Schema( + { + vol.Required("confirm_reset", default=False): selector.BooleanSelector(), + } + ) diff --git a/custom_components/tibber_prices/config_flow_handlers/subentry_flow.py b/custom_components/tibber_prices/config_flow_handlers/subentry_flow.py index 6f813c7..d8d207c 100644 --- a/custom_components/tibber_prices/config_flow_handlers/subentry_flow.py +++ b/custom_components/tibber_prices/config_flow_handlers/subentry_flow.py @@ -125,6 +125,9 @@ class TibberPricesSubentryFlowHandler(ConfigSubentryFlow): offset_desc = self._format_offset_description(offset_days, offset_hours, offset_minutes) subentry_title = f"{parent_entry.title} ({offset_desc})" + # Note: Subentries inherit options from parent entry automatically + # Options parameter is not supported by ConfigSubentryFlow.async_create_entry() + return self.async_create_entry( title=subentry_title, data={ diff --git a/custom_components/tibber_prices/config_flow_handlers/user_flow.py b/custom_components/tibber_prices/config_flow_handlers/user_flow.py index 972e837..116b157 100644 --- a/custom_components/tibber_prices/config_flow_handlers/user_flow.py +++ b/custom_components/tibber_prices/config_flow_handlers/user_flow.py @@ -20,7 +20,12 @@ from custom_components.tibber_prices.config_flow_handlers.validators import ( TibberPricesInvalidAuthError, validate_api_token, ) -from custom_components.tibber_prices.const import DOMAIN, LOGGER, get_translation +from custom_components.tibber_prices.const import ( + DOMAIN, + LOGGER, + get_default_options, + get_translation, +) from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, @@ -379,6 +384,16 @@ class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN): "user_login": self._user_login or "N/A", } + # Extract currency from home data for intelligent defaults + currency_code = None + if ( + selected_home + and (subscription := selected_home.get("currentSubscription")) + and (price_info := subscription.get("priceInfo")) + and (current_price := price_info.get("current")) + ): + currency_code = current_price.get("currency") + # Generate entry title from home address (not appNickname) entry_title = self._get_entry_title(selected_home) @@ -386,6 +401,7 @@ class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN): title=entry_title, data=data, description=f"{self._user_login} ({self._user_id})", + options=get_default_options(currency_code), ) home_options = [ diff --git a/custom_components/tibber_prices/const.py b/custom_components/tibber_prices/const.py index 1cf7c4a..57073e7 100644 --- a/custom_components/tibber_prices/const.py +++ b/custom_components/tibber_prices/const.py @@ -298,6 +298,72 @@ def get_default_currency_display(currency_code: str | None) -> str: return DEFAULT_CURRENCY_DISPLAY.get(currency_code.upper(), DISPLAY_MODE_SUBUNIT) +def get_default_options(currency_code: str | None) -> dict[str, Any]: + """ + Get complete default options for a new config entry. + + This ensures new config entries have explicitly set defaults based on their currency, + distinguishing them from legacy config entries that need migration. + + Options structure has been flattened for single-section steps: + - Flat values: extended_descriptions, average_sensor_display, currency_display_mode, + price_rating_thresholds, volatility_thresholds, price_trend_thresholds, time offsets + - Nested sections (multi-section steps only): period_settings, flexibility_settings, + relaxation_and_target_periods + + Args: + currency_code: ISO 4217 currency code (e.g., 'EUR', 'NOK') + + Returns: + Dictionary with all default option values in nested section structure + + """ + return { + # Flat configuration values + CONF_EXTENDED_DESCRIPTIONS: DEFAULT_EXTENDED_DESCRIPTIONS, + CONF_AVERAGE_SENSOR_DISPLAY: DEFAULT_AVERAGE_SENSOR_DISPLAY, + CONF_CURRENCY_DISPLAY_MODE: get_default_currency_display(currency_code), + CONF_VIRTUAL_TIME_OFFSET_DAYS: DEFAULT_VIRTUAL_TIME_OFFSET_DAYS, + CONF_VIRTUAL_TIME_OFFSET_HOURS: DEFAULT_VIRTUAL_TIME_OFFSET_HOURS, + CONF_VIRTUAL_TIME_OFFSET_MINUTES: DEFAULT_VIRTUAL_TIME_OFFSET_MINUTES, + # Price rating thresholds (flat - single-section step) + CONF_PRICE_RATING_THRESHOLD_LOW: DEFAULT_PRICE_RATING_THRESHOLD_LOW, + CONF_PRICE_RATING_THRESHOLD_HIGH: DEFAULT_PRICE_RATING_THRESHOLD_HIGH, + # Volatility thresholds (flat - single-section step) + CONF_VOLATILITY_THRESHOLD_MODERATE: DEFAULT_VOLATILITY_THRESHOLD_MODERATE, + CONF_VOLATILITY_THRESHOLD_HIGH: DEFAULT_VOLATILITY_THRESHOLD_HIGH, + CONF_VOLATILITY_THRESHOLD_VERY_HIGH: DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH, + # Price trend thresholds (flat - single-section step) + CONF_PRICE_TREND_THRESHOLD_RISING: DEFAULT_PRICE_TREND_THRESHOLD_RISING, + CONF_PRICE_TREND_THRESHOLD_FALLING: DEFAULT_PRICE_TREND_THRESHOLD_FALLING, + # Nested section: Period settings (shared by best/peak price) + "period_settings": { + CONF_BEST_PRICE_MIN_PERIOD_LENGTH: DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, + CONF_PEAK_PRICE_MIN_PERIOD_LENGTH: DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, + CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT: DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT, + CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT: DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, + CONF_BEST_PRICE_MAX_LEVEL: DEFAULT_BEST_PRICE_MAX_LEVEL, + CONF_PEAK_PRICE_MIN_LEVEL: DEFAULT_PEAK_PRICE_MIN_LEVEL, + }, + # Nested section: Flexibility settings (shared by best/peak price) + "flexibility_settings": { + CONF_BEST_PRICE_FLEX: DEFAULT_BEST_PRICE_FLEX, + CONF_PEAK_PRICE_FLEX: DEFAULT_PEAK_PRICE_FLEX, + CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG: DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG, + CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG: DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, + }, + # Nested section: Relaxation and target periods (shared by best/peak price) + "relaxation_and_target_periods": { + CONF_ENABLE_MIN_PERIODS_BEST: DEFAULT_ENABLE_MIN_PERIODS_BEST, + CONF_MIN_PERIODS_BEST: DEFAULT_MIN_PERIODS_BEST, + CONF_RELAXATION_ATTEMPTS_BEST: DEFAULT_RELAXATION_ATTEMPTS_BEST, + CONF_ENABLE_MIN_PERIODS_PEAK: DEFAULT_ENABLE_MIN_PERIODS_PEAK, + CONF_MIN_PERIODS_PEAK: DEFAULT_MIN_PERIODS_PEAK, + CONF_RELAXATION_ATTEMPTS_PEAK: DEFAULT_RELAXATION_ATTEMPTS_PEAK, + }, + } + + def get_display_unit_factor(config_entry: ConfigEntry) -> int: """ Get multiplication factor for converting base to display currency. diff --git a/custom_components/tibber_prices/coordinator/core.py b/custom_components/tibber_prices/coordinator/core.py index 347357a..a687b39 100644 --- a/custom_components/tibber_prices/coordinator/core.py +++ b/custom_components/tibber_prices/coordinator/core.py @@ -262,12 +262,21 @@ class TibberPricesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): getattr(_LOGGER, level)(prefixed_message, *args, **kwargs) async def _handle_options_update(self, _hass: HomeAssistant, _config_entry: ConfigEntry) -> None: - """Handle options update by invalidating config caches.""" + """Handle options update by invalidating config caches and re-transforming data.""" self._log("debug", "Options updated, invalidating config caches") self._data_transformer.invalidate_config_cache() self._period_calculator.invalidate_config_cache() - # Trigger a refresh to apply new configuration - await self.async_request_refresh() + + # Re-transform existing cached data with new configuration + # This updates rating_levels, volatility, and period calculations + # without needing to fetch new data from the API + if self._cached_price_data: + self._log("debug", "Re-transforming cached data with new configuration") + self.data = self._transform_data(self._cached_price_data) + # Notify all listeners about the updated data + self.async_update_listeners() + else: + self._log("warning", "No cached data available to re-transform") @callback def async_add_time_sensitive_listener(self, update_callback: TimeServiceCallback) -> CALLBACK_TYPE: diff --git a/custom_components/tibber_prices/coordinator/data_transformation.py b/custom_components/tibber_prices/coordinator/data_transformation.py index 9f5f6d4..69b7ac5 100644 --- a/custom_components/tibber_prices/coordinator/data_transformation.py +++ b/custom_components/tibber_prices/coordinator/data_transformation.py @@ -73,36 +73,52 @@ class TibberPricesDataTransformer: return self._config_cache # Build config dictionary (expensive operation) + options = self.config_entry.options + + # Best/peak price remain nested (multi-section steps) + best_period_section = options.get("period_settings", {}) + best_flex_section = options.get("flexibility_settings", {}) + best_relax_section = options.get("relaxation_and_target_periods", {}) + peak_period_section = options.get("period_settings", {}) + peak_flex_section = options.get("flexibility_settings", {}) + peak_relax_section = options.get("relaxation_and_target_periods", {}) + config = { "thresholds": self.get_threshold_percentages(), + # Volatility thresholds now flat (single-section step) "volatility_thresholds": { - "moderate": self.config_entry.options.get(_const.CONF_VOLATILITY_THRESHOLD_MODERATE, 15.0), - "high": self.config_entry.options.get(_const.CONF_VOLATILITY_THRESHOLD_HIGH, 25.0), - "very_high": self.config_entry.options.get(_const.CONF_VOLATILITY_THRESHOLD_VERY_HIGH, 40.0), + "moderate": options.get(_const.CONF_VOLATILITY_THRESHOLD_MODERATE, 15.0), + "high": options.get(_const.CONF_VOLATILITY_THRESHOLD_HIGH, 25.0), + "very_high": options.get(_const.CONF_VOLATILITY_THRESHOLD_VERY_HIGH, 40.0), + }, + # Price trend thresholds now flat (single-section step) + "price_trend_thresholds": { + "rising": options.get( + _const.CONF_PRICE_TREND_THRESHOLD_RISING, _const.DEFAULT_PRICE_TREND_THRESHOLD_RISING + ), + "falling": options.get( + _const.CONF_PRICE_TREND_THRESHOLD_FALLING, _const.DEFAULT_PRICE_TREND_THRESHOLD_FALLING + ), }, "best_price_config": { - "flex": self.config_entry.options.get(_const.CONF_BEST_PRICE_FLEX, 15.0), - "max_level": self.config_entry.options.get(_const.CONF_BEST_PRICE_MAX_LEVEL, "NORMAL"), - "min_period_length": self.config_entry.options.get(_const.CONF_BEST_PRICE_MIN_PERIOD_LENGTH, 4), - "min_distance_from_avg": self.config_entry.options.get( - _const.CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, -5.0 - ), - "max_level_gap_count": self.config_entry.options.get(_const.CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, 0), - "enable_min_periods": self.config_entry.options.get(_const.CONF_ENABLE_MIN_PERIODS_BEST, False), - "min_periods": self.config_entry.options.get(_const.CONF_MIN_PERIODS_BEST, 2), - "relaxation_attempts": self.config_entry.options.get(_const.CONF_RELAXATION_ATTEMPTS_BEST, 4), + "flex": best_flex_section.get(_const.CONF_BEST_PRICE_FLEX, 15.0), + "max_level": best_period_section.get(_const.CONF_BEST_PRICE_MAX_LEVEL, "NORMAL"), + "min_period_length": best_period_section.get(_const.CONF_BEST_PRICE_MIN_PERIOD_LENGTH, 4), + "min_distance_from_avg": best_flex_section.get(_const.CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, -5.0), + "max_level_gap_count": best_period_section.get(_const.CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, 0), + "enable_min_periods": best_relax_section.get(_const.CONF_ENABLE_MIN_PERIODS_BEST, False), + "min_periods": best_relax_section.get(_const.CONF_MIN_PERIODS_BEST, 2), + "relaxation_attempts": best_relax_section.get(_const.CONF_RELAXATION_ATTEMPTS_BEST, 4), }, "peak_price_config": { - "flex": self.config_entry.options.get(_const.CONF_PEAK_PRICE_FLEX, 15.0), - "min_level": self.config_entry.options.get(_const.CONF_PEAK_PRICE_MIN_LEVEL, "HIGH"), - "min_period_length": self.config_entry.options.get(_const.CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, 4), - "min_distance_from_avg": self.config_entry.options.get( - _const.CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, 5.0 - ), - "max_level_gap_count": self.config_entry.options.get(_const.CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, 0), - "enable_min_periods": self.config_entry.options.get(_const.CONF_ENABLE_MIN_PERIODS_PEAK, False), - "min_periods": self.config_entry.options.get(_const.CONF_MIN_PERIODS_PEAK, 2), - "relaxation_attempts": self.config_entry.options.get(_const.CONF_RELAXATION_ATTEMPTS_PEAK, 4), + "flex": peak_flex_section.get(_const.CONF_PEAK_PRICE_FLEX, 15.0), + "min_level": peak_period_section.get(_const.CONF_PEAK_PRICE_MIN_LEVEL, "HIGH"), + "min_period_length": peak_period_section.get(_const.CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, 4), + "min_distance_from_avg": peak_flex_section.get(_const.CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, 5.0), + "max_level_gap_count": peak_period_section.get(_const.CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, 0), + "enable_min_periods": peak_relax_section.get(_const.CONF_ENABLE_MIN_PERIODS_PEAK, False), + "min_periods": peak_relax_section.get(_const.CONF_MIN_PERIODS_PEAK, 2), + "relaxation_attempts": peak_relax_section.get(_const.CONF_RELAXATION_ATTEMPTS_PEAK, 4), }, } diff --git a/custom_components/tibber_prices/coordinator/periods.py b/custom_components/tibber_prices/coordinator/periods.py index 605b315..7fd0d12 100644 --- a/custom_components/tibber_prices/coordinator/periods.py +++ b/custom_components/tibber_prices/coordinator/periods.py @@ -92,8 +92,9 @@ class TibberPricesPeriodCalculator: # Get level filter overrides from options options = self.config_entry.options - best_level_filter = options.get(_const.CONF_BEST_PRICE_MAX_LEVEL, _const.DEFAULT_BEST_PRICE_MAX_LEVEL) - peak_level_filter = options.get(_const.CONF_PEAK_PRICE_MIN_LEVEL, _const.DEFAULT_PEAK_PRICE_MIN_LEVEL) + period_settings = options.get("period_settings", {}) + best_level_filter = period_settings.get(_const.CONF_BEST_PRICE_MAX_LEVEL, _const.DEFAULT_BEST_PRICE_MAX_LEVEL) + peak_level_filter = period_settings.get(_const.CONF_PEAK_PRICE_MIN_LEVEL, _const.DEFAULT_PEAK_PRICE_MIN_LEVEL) # Compute hash from all relevant data hash_data = ( @@ -124,33 +125,36 @@ class TibberPricesPeriodCalculator: self._config_cache = {} options = self.config_entry.options - data = self.config_entry.data + + # Get nested sections from options + # CRITICAL: Best/Peak price settings are stored in nested sections: + # - period_settings: min_period_length, max_level, gap_count + # - flexibility_settings: flex, min_distance_from_avg + # These settings are ONLY in options (not in data), structured since initial config flow + period_settings = options.get("period_settings", {}) + flexibility_settings = options.get("flexibility_settings", {}) if reverse_sort: # Peak price configuration - flex = options.get( - _const.CONF_PEAK_PRICE_FLEX, data.get(_const.CONF_PEAK_PRICE_FLEX, _const.DEFAULT_PEAK_PRICE_FLEX) - ) - min_distance_from_avg = options.get( + flex = flexibility_settings.get(_const.CONF_PEAK_PRICE_FLEX, _const.DEFAULT_PEAK_PRICE_FLEX) + min_distance_from_avg = flexibility_settings.get( _const.CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, - data.get(_const.CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, _const.DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG), + _const.DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, ) - min_period_length = options.get( + min_period_length = period_settings.get( _const.CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, - data.get(_const.CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, _const.DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH), + _const.DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, ) else: # Best price configuration - flex = options.get( - _const.CONF_BEST_PRICE_FLEX, data.get(_const.CONF_BEST_PRICE_FLEX, _const.DEFAULT_BEST_PRICE_FLEX) - ) - min_distance_from_avg = options.get( + flex = flexibility_settings.get(_const.CONF_BEST_PRICE_FLEX, _const.DEFAULT_BEST_PRICE_FLEX) + min_distance_from_avg = flexibility_settings.get( _const.CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, - data.get(_const.CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, _const.DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG), + _const.DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG, ) - min_period_length = options.get( + min_period_length = period_settings.get( _const.CONF_BEST_PRICE_MIN_PERIOD_LENGTH, - data.get(_const.CONF_BEST_PRICE_MIN_PERIOD_LENGTH, _const.DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH), + _const.DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, ) # Convert flex from percentage to decimal (e.g., 5 -> 0.05) @@ -356,13 +360,14 @@ class TibberPricesPeriodCalculator: # Normal check failed - try splitting at gap clusters as fallback # Get minimum period length from config (convert minutes to intervals) + period_settings = self.config_entry.options.get("period_settings", {}) if reverse_sort: - min_period_minutes = self.config_entry.options.get( + min_period_minutes = period_settings.get( _const.CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, _const.DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, ) else: - min_period_minutes = self.config_entry.options.get( + min_period_minutes = period_settings.get( _const.CONF_BEST_PRICE_MIN_PERIOD_LENGTH, _const.DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, ) @@ -487,13 +492,15 @@ class TibberPricesPeriodCalculator: # Get appropriate config based on sensor type elif reverse_sort: # Peak price: minimum level filter (lower bound) - level_config = self.config_entry.options.get( + period_settings = self.config_entry.options.get("period_settings", {}) + level_config = period_settings.get( _const.CONF_PEAK_PRICE_MIN_LEVEL, _const.DEFAULT_PEAK_PRICE_MIN_LEVEL, ) else: # Best price: maximum level filter (upper bound) - level_config = self.config_entry.options.get( + period_settings = self.config_entry.options.get("period_settings", {}) + level_config = period_settings.get( _const.CONF_BEST_PRICE_MAX_LEVEL, _const.DEFAULT_BEST_PRICE_MAX_LEVEL, ) @@ -511,13 +518,14 @@ class TibberPricesPeriodCalculator: return True # If no data, don't filter # Get gap tolerance configuration + period_settings = self.config_entry.options.get("period_settings", {}) if reverse_sort: - max_gap_count = self.config_entry.options.get( + max_gap_count = period_settings.get( _const.CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, _const.DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, ) else: - max_gap_count = self.config_entry.options.get( + max_gap_count = period_settings.get( _const.CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, _const.DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT, ) @@ -574,7 +582,8 @@ class TibberPricesPeriodCalculator: coordinator_data = {"priceInfo": price_info} all_prices = get_intervals_for_day_offsets(coordinator_data, [-2, -1, 0, 1]) - # Get rating thresholds from config + # Get rating thresholds from config (flat in options, not in sections) + # CRITICAL: Price rating thresholds are stored FLAT in options (no sections) threshold_low = self.config_entry.options.get( _const.CONF_PRICE_RATING_THRESHOLD_LOW, _const.DEFAULT_PRICE_RATING_THRESHOLD_LOW, @@ -584,7 +593,8 @@ class TibberPricesPeriodCalculator: _const.DEFAULT_PRICE_RATING_THRESHOLD_HIGH, ) - # Get volatility thresholds from config + # Get volatility thresholds from config (flat in options, not in sections) + # CRITICAL: Volatility thresholds are stored FLAT in options (no sections) threshold_volatility_moderate = self.config_entry.options.get( _const.CONF_VOLATILITY_THRESHOLD_MODERATE, _const.DEFAULT_VOLATILITY_THRESHOLD_MODERATE, @@ -599,7 +609,9 @@ class TibberPricesPeriodCalculator: ) # Get relaxation configuration for best price - enable_relaxation_best = self.config_entry.options.get( + # CRITICAL: Relaxation settings are stored in nested section 'relaxation_and_target_periods' + relaxation_and_target_periods = self.config_entry.options.get("relaxation_and_target_periods", {}) + enable_relaxation_best = relaxation_and_target_periods.get( _const.CONF_ENABLE_MIN_PERIODS_BEST, _const.DEFAULT_ENABLE_MIN_PERIODS_BEST, ) @@ -611,11 +623,11 @@ class TibberPricesPeriodCalculator: show_best_price = bool(all_prices) else: show_best_price = self.should_show_periods(price_info, reverse_sort=False) if all_prices else False - min_periods_best = self.config_entry.options.get( + min_periods_best = relaxation_and_target_periods.get( _const.CONF_MIN_PERIODS_BEST, _const.DEFAULT_MIN_PERIODS_BEST, ) - relaxation_attempts_best = self.config_entry.options.get( + relaxation_attempts_best = relaxation_and_target_periods.get( _const.CONF_RELAXATION_ATTEMPTS_BEST, _const.DEFAULT_RELAXATION_ATTEMPTS_BEST, ) @@ -623,12 +635,14 @@ class TibberPricesPeriodCalculator: # Calculate best price periods (or return empty if filtered) if show_best_price: best_config = self.get_period_config(reverse_sort=False) - # Get level filter configuration - max_level_best = self.config_entry.options.get( + # Get level filter configuration from period_settings section + # CRITICAL: max_level and gap_count are stored in nested section 'period_settings' + period_settings = self.config_entry.options.get("period_settings", {}) + max_level_best = period_settings.get( _const.CONF_BEST_PRICE_MAX_LEVEL, _const.DEFAULT_BEST_PRICE_MAX_LEVEL, ) - gap_count_best = self.config_entry.options.get( + gap_count_best = period_settings.get( _const.CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT, _const.DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT, ) @@ -672,7 +686,8 @@ class TibberPricesPeriodCalculator: } # Get relaxation configuration for peak price - enable_relaxation_peak = self.config_entry.options.get( + # CRITICAL: Relaxation settings are stored in nested section 'relaxation_and_target_periods' + enable_relaxation_peak = relaxation_and_target_periods.get( _const.CONF_ENABLE_MIN_PERIODS_PEAK, _const.DEFAULT_ENABLE_MIN_PERIODS_PEAK, ) @@ -684,11 +699,11 @@ class TibberPricesPeriodCalculator: show_peak_price = bool(all_prices) else: show_peak_price = self.should_show_periods(price_info, reverse_sort=True) if all_prices else False - min_periods_peak = self.config_entry.options.get( + min_periods_peak = relaxation_and_target_periods.get( _const.CONF_MIN_PERIODS_PEAK, _const.DEFAULT_MIN_PERIODS_PEAK, ) - relaxation_attempts_peak = self.config_entry.options.get( + relaxation_attempts_peak = relaxation_and_target_periods.get( _const.CONF_RELAXATION_ATTEMPTS_PEAK, _const.DEFAULT_RELAXATION_ATTEMPTS_PEAK, ) @@ -696,12 +711,13 @@ class TibberPricesPeriodCalculator: # Calculate peak price periods (or return empty if filtered) if show_peak_price: peak_config = self.get_period_config(reverse_sort=True) - # Get level filter configuration - min_level_peak = self.config_entry.options.get( + # Get level filter configuration from period_settings section + # CRITICAL: min_level and gap_count are stored in nested section 'period_settings' + min_level_peak = period_settings.get( _const.CONF_PEAK_PRICE_MIN_LEVEL, _const.DEFAULT_PEAK_PRICE_MIN_LEVEL, ) - gap_count_peak = self.config_entry.options.get( + gap_count_peak = period_settings.get( _const.CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, _const.DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT, ) diff --git a/custom_components/tibber_prices/sensor/core.py b/custom_components/tibber_prices/sensor/core.py index af6bc66..f85b910 100644 --- a/custom_components/tibber_prices/sensor/core.py +++ b/custom_components/tibber_prices/sensor/core.py @@ -289,6 +289,8 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor): # Clear cached trend values when coordinator data changes if self.entity_description.key.startswith("price_trend_"): self._trend_calculator.clear_trend_cache() + # Also clear calculation cache (e.g., when threshold config changes) + self._trend_calculator.clear_calculation_cache() # Refresh chart data when coordinator updates (new price data or user data) if self.entity_description.key == "chart_data_export": diff --git a/custom_components/tibber_prices/services/get_chartdata.py b/custom_components/tibber_prices/services/get_chartdata.py index 27ad60f..8bec2e3 100644 --- a/custom_components/tibber_prices/services/get_chartdata.py +++ b/custom_components/tibber_prices/services/get_chartdata.py @@ -146,12 +146,12 @@ def _calculate_metadata( # noqa: PLR0912, PLR0913, PLR0915 return {} min_val = min(data) max_val = max(data) - avg_val = sum(data) / len(data) + mean_val = sum(data) / len(data) median_val = sorted(data)[len(data) // 2] - # Calculate avg_position and median_position (0-1 scale) + # Calculate mean_position and median_position (0-1 scale) price_range = max_val - min_val - avg_position = (avg_val - min_val) / price_range if price_range > 0 else 0.5 + mean_position = (mean_val - min_val) / price_range if price_range > 0 else 0.5 median_position = (median_val - min_val) / price_range if price_range > 0 else 0.5 # Position precision: 2 decimals for subunit currency, 4 for base currency @@ -162,8 +162,8 @@ def _calculate_metadata( # noqa: PLR0912, PLR0913, PLR0915 return { "min": round(min_val, price_decimals), "max": round(max_val, price_decimals), - "avg": round(avg_val, price_decimals), - "avg_position": round(avg_position, position_decimals), + "mean": round(mean_val, price_decimals), + "mean_position": round(mean_position, position_decimals), "median": round(median_val, price_decimals), "median_position": round(median_position, position_decimals), } diff --git a/custom_components/tibber_prices/translations/de.json b/custom_components/tibber_prices/translations/de.json index 380d41a..7a97b72 100644 --- a/custom_components/tibber_prices/translations/de.json +++ b/custom_components/tibber_prices/translations/de.json @@ -132,8 +132,22 @@ "options": { "step": { "init": { + "menu_options": { + "general_settings": "⚙️ Allgemeine Einstellungen", + "display_settings": "💱 Währungsanzeige", + "current_interval_price_rating": "📊 Preisbewertung", + "volatility": "💨 Preis-Volatilität", + "best_price": "💚 Bestpreis", + "peak_price": "🔴 Spitzenpreis", + "price_trend": "📈 Preistrend", + "chart_data_export": "📊 Diagrammdaten-Export", + "reset_to_defaults": "🔄 Auf Werkseinstellungen zurücksetzen", + "finish": "⬅️ Zurück" + } + }, + "general_settings": { "title": "⚙️ Allgemeine Einstellungen", - "description": "_{step_progress}_\n\n**Konfiguriere allgemeine Einstellungen für Tibber-Preisinformationen und -bewertungen.**\n\n---\n\n**Benutzer:** {user_login}", + "description": "**Konfiguriere allgemeine Einstellungen für Tibber-Preisinformationen und -bewertungen.**\n\n---\n\n**Benutzer:** {user_login}", "data": { "extended_descriptions": "Erweiterte Beschreibungen", "average_sensor_display": "Durchschnittsensor-Anzeige" @@ -142,43 +156,35 @@ "extended_descriptions": "Steuert, ob Entitätsattribute ausführliche Erklärungen und Nutzungstipps enthalten.\n\n• Deaktiviert (Standard): Nur kurze Beschreibung\n• Aktiviert: Ausführliche Erklärung + praktische Nutzungsbeispiele\n\nBeispiel:\nDeaktiviert = 1 Attribut\nAktiviert = 2 zusätzliche Attribute", "average_sensor_display": "Wähle aus, welcher statistische Wert im Sensorstatus für Durchschnitts-Preissensoren angezeigt wird. Der andere Wert wird als Attribut angezeigt. Der Median ist resistenter gegen Extremwerte, während das arithmetische Mittel dem traditionellen Durchschnitt entspricht. Standard: Median" }, - "submit": "Weiter →" + "submit": "↩ Speichern & Zurück" }, "display_settings": { "title": "💱 Währungsanzeige-Einstellungen", - "description": "_{step_progress}_\n\n**Konfiguriere, wie Strompreise angezeigt werden - in Basiswährung (€, kr) oder Unterwährungseinheit (ct, øre).**\n\n---", + "description": "**Konfiguriere, wie Strompreise angezeigt werden - in Basiswährung (€, kr) oder Unterwährungseinheit (ct, øre).**\n\n---", "data": { "currency_display_mode": "Anzeigemodus" }, "data_description": { "currency_display_mode": "Wähle, wie Preise angezeigt werden:\n\n• **Basiswährung** (€/kWh, kr/kWh): Dezimalwerte (z.B. 0,25 €/kWh) - Unterschiede sichtbar ab 3.-4. Nachkommastelle\n• **Unterwährungseinheit** (ct/kWh, øre/kWh): Größere Werte (z.B. 25,00 ct/kWh) - Unterschiede bereits ab 1. Nachkommastelle sichtbar\n\nStandard abhängig von deiner Währung:\n• EUR → Unterwährungseinheit (Cent) - deutsche/niederländische Präferenz\n• NOK/SEK/DKK → Basiswährung (Kronen) - skandinavische Präferenz\n• USD/GBP → Basiswährung\n\n**💡 Tipp:** Bei Auswahl von Unterwährungseinheit kannst du den zusätzlichen Sensor \"Aktueller Strompreis (Energie-Dashboard)\" aktivieren (standardmäßig deaktiviert)." }, - "submit": "Weiter →" + "submit": "↩ Speichern & Zurück" }, "current_interval_price_rating": { "title": "📊 Preisbewertungs-Schwellenwerte", - "description": "_{step_progress}_\n\n**Konfiguriere Schwellenwerte für Preisbewertungsstufen (niedrig/normal/hoch) basierend auf dem Vergleich mit dem nachlaufenden 24-Stunden-Durchschnitt.**\n\n---", - "sections": { - "price_rating_thresholds": { - "name": "Preisbewertungs-Schwellenwerte", - "description": "Definiere die Einstufungen für die Preisbewertung.", - "data": { - "price_rating_threshold_low": "Niedrig-Schwelle", - "price_rating_threshold_high": "Hoch-Schwelle", - "average_sensor_display": "Durchschnitts-Sensor Anzeige" - }, - "data_description": { - "price_rating_threshold_low": "Prozentwert, um wie viel der aktuelle Preis unter dem nachlaufenden 24-Stunden-Durchschnitt liegen muss, damit er als 'niedrig' bewertet wird. Beispiel: 5 bedeutet mindestens 5% unter Durchschnitt. Sensoren mit dieser Bewertung zeigen günstige Zeitfenster an. Standard: 5%", - "price_rating_threshold_high": "Prozentwert, um wie viel der aktuelle Preis über dem nachlaufenden 24-Stunden-Durchschnitt liegen muss, damit er als 'hoch' bewertet wird. Beispiel: 10 bedeutet mindestens 10% über Durchschnitt. Sensoren mit dieser Bewertung warnen vor teuren Zeitfenstern. Standard: 10%", - "average_sensor_display": "Wähle, welches statistische Maß im Sensor-Status für Durchschnittspreissensoren angezeigt werden soll. Der andere Wert wird als Attribut angezeigt. Der Median ist widerstandsfähiger gegen Extremwerte, während das arithmetische Mittel den traditionellen Durchschnitt darstellt. Standard: Median" - } - } + "description": "**Konfiguriere Schwellenwerte für Preisbewertungsstufen (niedrig/normal/hoch) basierend auf dem Vergleich mit dem nachlaufenden 24-Stunden-Durchschnitt.**", + "data": { + "price_rating_threshold_low": "Niedrig-Schwelle", + "price_rating_threshold_high": "Hoch-Schwelle" }, - "submit": "Weiter →" + "data_description": { + "price_rating_threshold_low": "Prozentwert, um wie viel der aktuelle Preis unter dem nachlaufenden 24-Stunden-Durchschnitt liegen muss, damit er als 'niedrig' bewertet wird. Beispiel: 5 bedeutet mindestens 5% unter Durchschnitt. Sensoren mit dieser Bewertung zeigen günstige Zeitfenster an. Standard: 5%", + "price_rating_threshold_high": "Prozentwert, um wie viel der aktuelle Preis über dem nachlaufenden 24-Stunden-Durchschnitt liegen muss, damit er als 'hoch' bewertet wird. Beispiel: 10 bedeutet mindestens 10% über Durchschnitt. Sensoren mit dieser Bewertung warnen vor teuren Zeitfenstern. Standard: 10%" + }, + "submit": "↩ Speichern & Zurück" }, "best_price": { "title": "💚 Bestpreis-Zeitraum Einstellungen", - "description": "_{step_progress}_\n\n**Konfiguration für den Bestpreis-Zeitraum mit den niedrigsten Strompreisen.**\n\n---", + "description": "**Konfiguration für den Bestpreis-Zeitraum mit den niedrigsten Strompreisen.**\n\n---", "sections": { "period_settings": { "name": "Zeitraumdauer & Preisniveaus", @@ -221,11 +227,11 @@ } } }, - "submit": "Weiter →" + "submit": "↩ Speichern & Zurück" }, "peak_price": { "title": "🔴 Spitzenpreis-Zeitraum Einstellungen", - "description": "_{step_progress}_\n\n**Konfiguration für den Spitzenpreis-Zeitraum mit den höchsten Strompreisen.**\n\n---", + "description": "**Konfiguration für den Spitzenpreis-Zeitraum mit den höchsten Strompreisen.**\n\n---", "sections": { "period_settings": { "name": "Zeitraum-Einstellungen", @@ -268,52 +274,48 @@ } } }, - "submit": "Weiter →" + "submit": "↩ Speichern & Zurück" }, "price_trend": { "title": "📈 Preistrend-Schwellenwerte", - "description": "_{step_progress}_\n\n**Konfiguriere Schwellenwerte für Preistrend-Sensoren. Diese Sensoren vergleichen den aktuellen Preis mit dem Durchschnitt der nächsten N Stunden, um festzustellen, ob die Preise steigen, fallen oder stabil sind.**\n\n---", - "sections": { - "price_trend_thresholds": { - "name": "Preistrend-Schwellenwerte", - "description": "Definiere die Einstufungen für den Preistrend.", - "data": { - "price_trend_threshold_rising": "Steigend-Schwelle", - "price_trend_threshold_falling": "Fallend-Schwelle" - }, - "data_description": { - "price_trend_threshold_rising": "Prozentwert, um wie viel der Durchschnitt der nächsten N Stunden über dem aktuellen Preis liegen muss, damit der Trend als 'steigend' gilt. Beispiel: 5 bedeutet Durchschnitt ist mindestens 5% höher → Preise werden steigen. Typische Werte: 5-15%. Standard: 5%", - "price_trend_threshold_falling": "Prozentwert (negativ), um wie viel der Durchschnitt der nächsten N Stunden unter dem aktuellen Preis liegen muss, damit der Trend als 'fallend' gilt. Beispiel: -5 bedeutet Durchschnitt ist mindestens 5% niedriger → Preise werden fallen. Typische Werte: -5 bis -15%. Standard: -5%" - } - } + "description": "**Konfiguriere Schwellenwerte für Preistrend-Sensoren. Diese Sensoren vergleichen den aktuellen Preis mit dem Durchschnitt der nächsten N Stunden, um festzustellen, ob die Preise steigen, fallen oder stabil sind.**", + "data": { + "price_trend_threshold_rising": "Steigend-Schwelle", + "price_trend_threshold_falling": "Fallend-Schwelle" }, - "submit": "Weiter →" + "data_description": { + "price_trend_threshold_rising": "Prozentwert, um wie viel der Durchschnitt der nächsten N Stunden über dem aktuellen Preis liegen muss, damit der Trend als 'steigend' gilt. Beispiel: 5 bedeutet Durchschnitt ist mindestens 5% höher → Preise werden steigen. Typische Werte: 5-15%. Standard: 5%", + "price_trend_threshold_falling": "Prozentwert (negativ), um wie viel der Durchschnitt der nächsten N Stunden unter dem aktuellen Preis liegen muss, damit der Trend als 'fallend' gilt. Beispiel: -5 bedeutet Durchschnitt ist mindestens 5% niedriger → Preise werden fallen. Typische Werte: -5 bis -15%. Standard: -5%" + }, + "submit": "↩ Speichern & Zurück" }, "volatility": { "title": "💨 Volatilität Schwellenwerte", - "description": "_{step_progress}_\n\n**Konfiguriere Schwellenwerte für die Volatilitätsklassifizierung.** Volatilität misst relative Preisschwankungen anhand des Variationskoeffizienten (VK = Standardabweichung / Durchschnitt × 100%). Diese Schwellenwerte sind Prozentwerte, die für alle Preisniveaus funktionieren.\n\nVerwendet von:\n• Volatilitätssensoren (Klassifizierung)\n• Trend-Sensoren (adaptive Schwellenanpassung: <moderat = empfindlicher, ≥hoch = weniger empfindlich)\n\n---", - "sections": { - "volatility_thresholds": { - "name": "Volatilitätsschwellen", - "description": "Definiere Volatilitäts-Klassifizierungsstufen.", - "data": { - "volatility_threshold_moderate": "Moderat-Schwelle", - "volatility_threshold_high": "Hoch-Schwelle", - "volatility_threshold_very_high": "Sehr hoch-Schwelle" - }, - "data_description": { - "volatility_threshold_moderate": "Variationskoeffizient (VK) ab dem Preise als 'moderat volatil' gelten. VK = (Standardabweichung / Durchschnitt) × 100%. Beispiel: 15 bedeutet Preisschwankungen von ±15% um den Durchschnitt. Sensoren zeigen diese Klassifizierung an, Trend-Sensoren werden empfindlicher. Standard: 15%", - "volatility_threshold_high": "Variationskoeffizient (VK) ab dem Preise als 'hoch volatil' gelten. Beispiel: 30 bedeutet Preisschwankungen von ±30% um den Durchschnitt. Größere Preissprünge erwartet, Trend-Sensoren werden weniger empfindlich. Standard: 30%", - "volatility_threshold_very_high": "Variationskoeffizient (VK) ab dem Preise als 'sehr hoch volatil' gelten. Beispiel: 50 bedeutet extreme Preisschwankungen von ±50% um den Durchschnitt. An solchen Tagen sind starke Preisspitzen wahrscheinlich. Standard: 50%" - } - } + "description": "**Konfiguriere Schwellenwerte für die Volatilitätsklassifizierung.** Volatilität misst relative Preisschwankungen anhand des Variationskoeffizienten (VK = Standardabweichung / Durchschnitt × 100%). Diese Schwellenwerte sind Prozentwerte, die für alle Preisniveaus funktionieren.\n\nVerwendet von:\n• Volatilitätssensoren (Klassifizierung)\n• Trend-Sensoren (adaptive Schwellenanpassung: <moderat = empfindlicher, ≥hoch = weniger empfindlich)", + "data": { + "volatility_threshold_moderate": "Moderat-Schwelle", + "volatility_threshold_high": "Hoch-Schwelle", + "volatility_threshold_very_high": "Sehr hoch-Schwelle" }, - "submit": "Weiter →" + "data_description": { + "volatility_threshold_moderate": "Variationskoeffizient (VK) ab dem Preise als 'moderat volatil' gelten. VK = (Standardabweichung / Durchschnitt) × 100%. Beispiel: 15 bedeutet Preisschwankungen von ±15% um den Durchschnitt. Sensoren zeigen diese Klassifizierung an, Trend-Sensoren werden empfindlicher. Standard: 15%", + "volatility_threshold_high": "Variationskoeffizient (VK) ab dem Preise als 'hoch volatil' gelten. Beispiel: 30 bedeutet Preisschwankungen von ±30% um den Durchschnitt. Größere Preissprünge erwartet, Trend-Sensoren werden weniger empfindlich. Standard: 30%", + "volatility_threshold_very_high": "Variationskoeffizient (VK) ab dem Preise als 'sehr hoch volatil' gelten. Beispiel: 50 bedeutet extreme Preisschwankungen von ±50% um den Durchschnitt. An solchen Tagen sind starke Preisspitzen wahrscheinlich. Standard: 50%" + }, + "submit": "↩ Speichern & Zurück" }, "chart_data_export": { "title": "📊 Chart Data Export Sensor", - "description": "_{step_progress}_\n\nDer Chart Data Export Sensor stellt Preisdaten als Sensor-Attribute zur Verfügung.\n\n⚠️ **Hinweis:** Dieser Sensor ist ein Legacy-Feature für Kompatibilität mit älteren Tools.\n\n**Für neue Setups empfohlen:** Nutze den `tibber_prices.get_chartdata` **Service direkt** - er ist flexibler, effizienter und der moderne Home Assistant-Ansatz.\n\n**Wann dieser Sensor sinnvoll ist:**\n\n✅ Dein Dashboard-Tool kann **nur** Attribute lesen (keine Service-Aufrufe)\n✅ Du brauchst statische Daten, die automatisch aktualisiert werden\n❌ **Nicht für Automationen:** Nutze dort direkt `tibber_prices.get_chartdata` - flexibler und effizienter!\n\n---\n\n**Sensor aktivieren:**\n\n1. Öffne **Einstellungen → Geräte & Dienste → Tibber Prices**\n2. Wähle dein Home → Finde **'Chart Data Export'** (Diagnose-Bereich)\n3. **Aktiviere den Sensor** (standardmäßig deaktiviert)\n\n**Konfiguration (optional):**\n\nStandardeinstellung funktioniert sofort (heute+morgen, 15-Minuten-Intervalle, reine Preise).\n\nFür Anpassungen füge in **`configuration.yaml`** ein:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle Parameter:** Siehe `tibber_prices.get_chartdata` Service-Dokumentation", - "submit": "Abschließen ✓" + "description": "Der Chart Data Export Sensor stellt Preisdaten als Sensor-Attribute zur Verfügung.\n\n⚠️ **Hinweis:** Dieser Sensor ist ein Legacy-Feature für Kompatibilität mit älteren Tools.\n\n**Für neue Setups empfohlen:** Nutze den `tibber_prices.get_chartdata` **Service direkt** - er ist flexibler, effizienter und der moderne Home Assistant-Ansatz.\n\n**Wann dieser Sensor sinnvoll ist:**\n\n✅ Dein Dashboard-Tool kann **nur** Attribute lesen (keine Service-Aufrufe)\n✅ Du brauchst statische Daten, die automatisch aktualisiert werden\n❌ **Nicht für Automationen:** Nutze dort direkt `tibber_prices.get_chartdata` - flexibler und effizienter!\n\n---\n\n**Sensor aktivieren:**\n\n1. Öffne **Einstellungen → Geräte & Dienste → Tibber Prices**\n2. Wähle dein Home → Finde **'Chart Data Export'** (Diagnose-Bereich)\n3. **Aktiviere den Sensor** (standardmäßig deaktiviert)\n\n**Konfiguration (optional):**\n\nStandardeinstellung funktioniert sofort (heute+morgen, 15-Minuten-Intervalle, reine Preise).\n\nFür Anpassungen füge in **`configuration.yaml`** ein:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle Parameter:** Siehe `tibber_prices.get_chartdata` Service-Dokumentation", + "submit": "↩ Ok & Zurück" + }, + "reset_to_defaults": { + "title": "🔄 Auf Werkseinstellungen zurücksetzen", + "description": "⚠️ **Warnung:** Dies setzt **ALLE** Einstellungen auf Werkseinstellungen zurück.\n\n**Was wird zurückgesetzt:**\n• Alle Preisbewertungs-Schwellwerte\n• Alle Volatilitäts-Schwellwerte\n• Alle Preistrend-Schwellwerte\n• Alle Einstellungen für Best-Price-Perioden\n• Alle Einstellungen für Peak-Price-Perioden\n• Anzeigeeinstellungen\n• Allgemeine Einstellungen\n\n**Was wird NICHT zurückgesetzt:**\n• Dein Tibber API-Token\n• Ausgewähltes Zuhause\n• Währung\n\n**💡 Tipp:** Nützlich, wenn du nach dem Experimentieren mit Einstellungen neu beginnen möchtest.", + "data": { + "confirm_reset": "Ja, alles auf Werkseinstellungen zurücksetzen" + }, + "submit": "Jetzt zurücksetzen" } }, "error": { @@ -341,7 +343,10 @@ "invalid_price_trend_falling": "Fallender Trendschwellenwert muss zwischen -50% und -1% liegen" }, "abort": { - "entry_not_found": "Tibber Konfigurationseintrag nicht gefunden." + "entry_not_found": "Tibber Konfigurationseintrag nicht gefunden.", + "reset_cancelled": "Zurücksetzen abgebrochen. Es wurden keine Änderungen an deiner Konfiguration vorgenommen.", + "reset_successful": "✅ Alle Einstellungen wurden auf Werkseinstellungen zurückgesetzt. Deine Konfiguration ist jetzt wie bei einer frischen Installation.", + "finished": "Konfiguration abgeschlossen." } }, "entity": { diff --git a/custom_components/tibber_prices/translations/en.json b/custom_components/tibber_prices/translations/en.json index 548d925..376379f 100644 --- a/custom_components/tibber_prices/translations/en.json +++ b/custom_components/tibber_prices/translations/en.json @@ -132,8 +132,22 @@ "options": { "step": { "init": { + "menu_options": { + "general_settings": "⚙️ General Settings", + "display_settings": "💱 Currency Display", + "current_interval_price_rating": "📊 Price Rating", + "volatility": "💨 Price Volatility", + "best_price": "💚 Best Price Period", + "peak_price": "🔴 Peak Price Period", + "price_trend": "📈 Price Trend", + "chart_data_export": "📊 Chart Data Export Sensor", + "reset_to_defaults": "🔄 Reset to Defaults", + "finish": "⬅️ Back" + } + }, + "general_settings": { "title": "⚙️ General Settings", - "description": "_{step_progress}_\n\n**Configure general settings for Tibber Price Information & Ratings.**\n\n---\n\n**User:** {user_login}", + "description": "**Configure general settings for Tibber Price Information & Ratings.**\n\n---\n\n**User:** {user_login}", "data": { "extended_descriptions": "Extended Descriptions", "average_sensor_display": "Average Sensor Display" @@ -142,41 +156,35 @@ "extended_descriptions": "Controls whether entity attributes include detailed explanations and usage tips.\n\n• Disabled (default): Brief description only\n• Enabled: Detailed explanation + practical usage examples\n\nExample:\nDisabled = 1 attribute\nEnabled = 2 additional attributes", "average_sensor_display": "Choose which statistical measure to display in the sensor state for average price sensors. The other value will be shown as an attribute. Median is more resistant to extreme values, while arithmetic mean represents the traditional average. Default: Median" }, - "submit": "Continue →" + "submit": "↩ Save & Back" }, "display_settings": { "title": "💱 Currency Display Settings", - "description": "_{step_progress}_\n\n**Configure how electricity prices are displayed - in base currency (€, kr) or subunit (ct, øre).**\n\n---", + "description": "**Configure how electricity prices are displayed - in base currency (€, kr) or subunit (ct, øre).**\n\n---", "data": { "currency_display_mode": "Display Mode" }, "data_description": { "currency_display_mode": "Choose how prices are displayed:\n\n• **Base Currency** (€/kWh, kr/kWh): Decimal values (e.g., 0.25 €/kWh) - differences visible from 3rd-4th decimal place\n• **Subunit Currency** (ct/kWh, øre/kWh): Larger values (e.g., 25.00 ct/kWh) - differences visible from 1st decimal place\n\nDefault depends on your currency:\n• EUR → Subunit (cents) - German/Dutch preference\n• NOK/SEK/DKK → Base (kroner) - Scandinavian preference\n• USD/GBP → Base currency\n\n**💡 Tip:** When selecting Subunit Currency, you can enable the additional \"Current Electricity Price (Energy Dashboard)\" sensor (disabled by default)." }, - "submit": "Continue →" + "submit": "↩ Save & Back" }, "current_interval_price_rating": { "title": "📊 Price Rating Thresholds", - "description": "_{step_progress}_\n\n**Configure thresholds for price rating levels (low/normal/high) based on comparison with trailing 24-hour average.**\n\n---", - "sections": { - "price_rating_thresholds": { - "name": "Price Rating Thresholds", - "description": "Define price rating classification levels.", - "data": { - "price_rating_threshold_low": "Low Threshold", - "price_rating_threshold_high": "High Threshold" - }, - "data_description": { - "price_rating_threshold_low": "Percentage below the trailing 24-hour average that the current price must be to qualify as 'low' rating. Example: 5 means at least 5% below average. Sensors with this rating indicate favorable time windows. Default: 5%", - "price_rating_threshold_high": "Percentage above the trailing 24-hour average that the current price must be to qualify as 'high' rating. Example: 10 means at least 10% above average. Sensors with this rating warn about expensive time windows. Default: 10%" - } - } + "description": "**Configure thresholds for price rating levels (low/normal/high) based on comparison with trailing 24-hour average.**", + "data": { + "price_rating_threshold_low": "Low Threshold", + "price_rating_threshold_high": "High Threshold" }, - "submit": "Continue →" + "data_description": { + "price_rating_threshold_low": "Percentage below the trailing 24-hour average that the current price must be to qualify as 'low' rating. Example: 5 means at least 5% below average. Sensors with this rating indicate favorable time windows. Default: 5%", + "price_rating_threshold_high": "Percentage above the trailing 24-hour average that the current price must be to qualify as 'high' rating. Example: 10 means at least 10% above average. Sensors with this rating warn about expensive time windows. Default: 10%" + }, + "submit": "↩ Save & Back" }, "best_price": { "title": "💚 Best Price Period Settings", - "description": "_{step_progress}_\n\n**Configure settings for the Best Price Period binary sensor. This sensor is active during periods with the lowest electricity prices.**\n\n---", + "description": "**Configure settings for the Best Price Period binary sensor. This sensor is active during periods with the lowest electricity prices.**\n\n---", "sections": { "period_settings": { "name": "Period Duration & Levels", @@ -219,11 +227,11 @@ } } }, - "submit": "Continue →" + "submit": "↩ Save & Back" }, "peak_price": { "title": "🔴 Peak Price Period Settings", - "description": "_{step_progress}_\n\n**Configure settings for the Peak Price Period binary sensor. This sensor is active during periods with the highest electricity prices.**\n\n---", + "description": "**Configure settings for the Peak Price Period binary sensor. This sensor is active during periods with the highest electricity prices.**\n\n---", "sections": { "period_settings": { "name": "Period Settings", @@ -266,52 +274,48 @@ } } }, - "submit": "Continue →" + "submit": "↩ Save & Back" }, "price_trend": { "title": "📈 Price Trend Thresholds", - "description": "_{step_progress}_\n\n**Configure thresholds for price trend sensors. These sensors compare current price with the average of the next N hours to determine if prices are rising, falling, or stable.**\n\n---", - "sections": { - "price_trend_thresholds": { - "name": "Price Trend Thresholds", - "description": "Define price trend classification levels.", - "data": { - "price_trend_threshold_rising": "Rising Threshold", - "price_trend_threshold_falling": "Falling Threshold" - }, - "data_description": { - "price_trend_threshold_rising": "Percentage that the average of the next N hours must be above the current price to qualify as 'rising' trend. Example: 5 means average is at least 5% higher → prices will rise. Typical values: 5-15%. Default: 5%", - "price_trend_threshold_falling": "Percentage (negative) that the average of the next N hours must be below the current price to qualify as 'falling' trend. Example: -5 means average is at least 5% lower → prices will fall. Typical values: -5 to -15%. Default: -5%" - } - } + "description": "**Configure thresholds for price trend sensors. These sensors compare current price with the average of the next N hours to determine if prices are rising, falling, or stable.**", + "data": { + "price_trend_threshold_rising": "Rising Threshold", + "price_trend_threshold_falling": "Falling Threshold" }, - "submit": "Continue →" - }, - "chart_data_export": { - "title": "📊 Chart Data Export Sensor", - "description": "_{step_progress}_\n\nThe Chart Data Export Sensor provides price data as sensor attributes.\n\n⚠️ **Note:** This sensor is a legacy feature for compatibility with older tools.\n\n**Recommended for new setups:** Use the `tibber_prices.get_chartdata` **service directly** - it's more flexible, efficient, and the modern Home Assistant approach.\n\n**When this sensor makes sense:**\n\n✅ Your dashboard tool can **only** read attributes (no service calls)\n✅ You need static data that updates automatically\n❌ **Not for automations:** Use `tibber_prices.get_chartdata` directly there - more flexible and efficient!\n\n---\n\n**Enable the sensor:**\n\n1. Open **Settings → Devices & Services → Tibber Prices**\n2. Select your home → Find **'Chart Data Export'** (Diagnostic section)\n3. **Enable the sensor** (disabled by default)\n\n**Configuration (optional):**\n\nDefault settings work out-of-the-box (today+tomorrow, 15-minute intervals, prices only).\n\nFor customization, add to **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**All parameters:** See `tibber_prices.get_chartdata` service documentation", - "submit": "Complete ✓" + "data_description": { + "price_trend_threshold_rising": "Percentage that the average of the next N hours must be above the current price to qualify as 'rising' trend. Example: 5 means average is at least 5% higher → prices will rise. Typical values: 5-15%. Default: 5%", + "price_trend_threshold_falling": "Percentage (negative) that the average of the next N hours must be below the current price to qualify as 'falling' trend. Example: -5 means average is at least 5% lower → prices will fall. Typical values: -5 to -15%. Default: -5%" + }, + "submit": "↩ Save & Back" }, "volatility": { "title": "💨 Price Volatility Thresholds", - "description": "_{step_progress}_\n\n**Configure thresholds for volatility classification.** Volatility measures relative price variation using the coefficient of variation (CV = standard deviation / mean × 100%). These thresholds are percentage values that work across all price levels.\n\nUsed by:\n• Volatility sensors (classification)\n• Trend sensors (adaptive threshold adjustment: <moderate = more sensitive, ≥high = less sensitive)\n\n---", - "sections": { - "volatility_thresholds": { - "name": "Volatility Thresholds", - "description": "Define price volatility classification levels.", - "data": { - "volatility_threshold_moderate": "Moderate Threshold", - "volatility_threshold_high": "High Threshold", - "volatility_threshold_very_high": "Very High Threshold" - }, - "data_description": { - "volatility_threshold_moderate": "Coefficient of Variation (CV) at which prices are considered 'moderately volatile'. CV = (standard deviation / mean) × 100%. Example: 15 means price fluctuations of ±15% around average. Sensors show this classification, trend sensors become more sensitive. Default: 15%", - "volatility_threshold_high": "Coefficient of Variation (CV) at which prices are considered 'highly volatile'. Example: 30 means price fluctuations of ±30% around average. Larger price jumps expected, trend sensors become less sensitive. Default: 30%", - "volatility_threshold_very_high": "Coefficient of Variation (CV) at which prices are considered 'very highly volatile'. Example: 50 means extreme price fluctuations of ±50% around average. On such days, strong price spikes are likely. Default: 50%" - } - } + "description": "**Configure thresholds for volatility classification.** Volatility measures relative price variation using the coefficient of variation (CV = standard deviation / mean × 100%). These thresholds are percentage values that work across all price levels.\n\nUsed by:\n• Volatility sensors (classification)\n• Trend sensors (adaptive threshold adjustment: <moderate = more sensitive, ≥high = less sensitive)", + "data": { + "volatility_threshold_moderate": "Moderate Threshold", + "volatility_threshold_high": "High Threshold", + "volatility_threshold_very_high": "Very High Threshold" }, - "submit": "Continue →" + "data_description": { + "volatility_threshold_moderate": "Coefficient of Variation (CV) at which prices are considered 'moderately volatile'. CV = (standard deviation / mean) × 100%. Example: 15 means price fluctuations of ±15% around average. Sensors show this classification, trend sensors become more sensitive. Default: 15%", + "volatility_threshold_high": "Coefficient of Variation (CV) at which prices are considered 'highly volatile'. Example: 30 means price fluctuations of ±30% around average. Larger price jumps expected, trend sensors become less sensitive. Default: 30%", + "volatility_threshold_very_high": "Coefficient of Variation (CV) at which prices are considered 'very highly volatile'. Example: 50 means extreme price fluctuations of ±50% around average. On such days, strong price spikes are likely. Default: 50%" + }, + "submit": "↩ Save & Back" + }, + "chart_data_export": { + "title": "📊 Chart Data Export Sensor", + "description": "The Chart Data Export Sensor provides price data as sensor attributes.\n\n⚠️ **Note:** This sensor is a legacy feature for compatibility with older tools.\n\n**Recommended for new setups:** Use the `tibber_prices.get_chartdata` **service directly** - it's more flexible, efficient, and the modern Home Assistant approach.\n\n**When this sensor makes sense:**\n\n✅ Your dashboard tool can **only** read attributes (no service calls)\n✅ You need static data that updates automatically\n❌ **Not for automations:** Use `tibber_prices.get_chartdata` directly there - more flexible and efficient!\n\n---\n\n**Enable the sensor:**\n\n1. Open **Settings → Devices & Services → Tibber Prices**\n2. Select your home → Find **'Chart Data Export'** (Diagnostic section)\n3. **Enable the sensor** (disabled by default)\n\n**Configuration (optional):**\n\nDefault settings work out-of-the-box (today+tomorrow, 15-minute intervals, prices only).\n\nFor customization, add to **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**All parameters:** See `tibber_prices.get_chartdata` service documentation", + "submit": "↩ Ok & Back" + }, + "reset_to_defaults": { + "title": "🔄 Reset to Defaults", + "description": "⚠️ **Warning:** This will reset **ALL** settings to factory defaults.\n\n**What will be reset:**\n• All price rating thresholds\n• All volatility thresholds\n• All price trend thresholds\n• All best price period settings\n• All peak price period settings\n• Display settings\n• General settings\n\n**What will NOT be reset:**\n• Your Tibber API token\n• Selected home\n• Currency\n\n**💡 Tip:** This is useful if you want to start fresh after experimenting with settings.", + "data": { + "confirm_reset": "Yes, reset everything to defaults" + }, + "submit": "Reset Now" } }, "error": { @@ -339,7 +343,10 @@ "invalid_price_trend_falling": "Falling trend threshold must be between -50% and -1%" }, "abort": { - "entry_not_found": "Tibber configuration entry not found." + "entry_not_found": "Tibber configuration entry not found.", + "reset_cancelled": "Reset cancelled. No changes were made to your configuration.", + "reset_successful": "✅ All settings have been reset to factory defaults. Your configuration is now like a fresh installation.", + "finished": "Configuration completed." } }, "entity": { diff --git a/custom_components/tibber_prices/translations/nb.json b/custom_components/tibber_prices/translations/nb.json index d371f36..20498ff 100644 --- a/custom_components/tibber_prices/translations/nb.json +++ b/custom_components/tibber_prices/translations/nb.json @@ -132,8 +132,22 @@ "options": { "step": { "init": { + "menu_options": { + "general_settings": "⚙️ Generelle innstillinger", + "display_settings": "💱 Valutavisning", + "current_interval_price_rating": "📊 Prisvurdering", + "volatility": "💨 Prisvolatilitet", + "best_price": "💚 Beste prisperiode", + "peak_price": "🔴 Toppprisperiode", + "price_trend": "📈 Pristrend", + "chart_data_export": "📊 Diagramdata-eksportsensor", + "reset_to_defaults": "🔄 Tilbakestill til standard", + "finish": "⬅️ Tilbake" + } + }, + "general_settings": { "title": "⚙️ Generelle innstillinger", - "description": "_{step_progress}_\n\n**Konfigurer generelle innstillinger for Tibber prisinformasjon og vurderinger.**\n\n---\n\n**Bruker:** {user_login}", + "description": "**Konfigurer generelle innstillinger for Tibber prisinformasjon og vurderinger.**\n\n---\n\n**Bruker:** {user_login}", "data": { "extended_descriptions": "Utvidede beskrivelser", "average_sensor_display": "Gjennomsnittssensor-visning" @@ -142,7 +156,7 @@ "extended_descriptions": "Styrer om entitetsattributter inkluderer detaljerte forklaringer og brukstips.\n\n• Deaktivert (standard): Bare kort beskrivelse\n• Aktivert: Detaljert forklaring + praktiske brukseksempler\n\nEksempel:\nDeaktivert = 1 attributt\nAktivert = 2 ekstra attributter", "average_sensor_display": "Velg hvilket statistisk mål som skal vises i sensortilstanden for gjennomsnittspris-sensorer. Den andre verdien vises som attributt. Median er mer motstandsdyktig mot ekstremverdier, mens aritmetisk gjennomsnitt representerer tradisjonelt gjennomsnitt. Standard: Median" }, - "submit": "Videre til trinn 2" + "submit": "↩ Lagre & tilbake" }, "display_settings": { "title": "💱 Valutavisningsinnstillinger", @@ -153,30 +167,24 @@ "data_description": { "currency_display_mode": "Velg hvordan priser vises:\n\n• **Basisvaluta** (€/kWh, kr/kWh): Desimalverdier (f.eks. 0,25 €/kWh) - forskjeller synlige fra 3.-4. desimalplass\n• **Underenhet** (ct/kWh, øre/kWh): Større verdier (f.eks. 25,00 ct/kWh) - forskjeller allerede synlige fra 1. desimalplass\n\nStandard avhenger av valutaen din:\n• EUR → Underenhet (cent) - tysk/nederlandsk preferanse\n• NOK/SEK/DKK → Basisvaluta (kroner) - skandinavisk preferanse\n• USD/GBP → Basisvaluta\n\n**💡 Tips:** Ved valg av underenhet kan du aktivere den ekstra sensoren \"Nåværende strømpris (Energi-dashboard)\" (deaktivert som standard)." }, - "submit": "Videre til trinn 3" + "submit": "↩ Lagre & tilbake" }, "current_interval_price_rating": { "title": "📊 Prisvurderings-terskler", - "description": "_{step_progress}_\n\n**Konfigurer terskler for prisvurderingsnivåer (lav/normal/høy) basert på sammenligning med etterfølgende 24-timers gjennomsnitt.**\n\n---", - "sections": { - "price_rating_thresholds": { - "name": "Prisvurderings-terskler", - "description": "Definer prisvurderingsnivåer.", - "data": { - "price_rating_threshold_low": "Lav-terskel", - "price_rating_threshold_high": "Høy-terskel" - }, - "data_description": { - "price_rating_threshold_low": "Prosentverdi for hvor mye gjeldende pris må være under det etterfølgende 24-timers gjennomsnittet for å kvalifisere som 'lav' vurdering. Eksempel: 5 betyr minst 5% under gjennomsnitt. Sensorer med denne vurderingen indikerer gunstige tidsvinduer. Standard: 5%", - "price_rating_threshold_high": "Prosentverdi for hvor mye gjeldende pris må være over det etterfølgende 24-timers gjennomsnittet for å kvalifisere som 'høy' vurdering. Eksempel: 10 betyr minst 10% over gjennomsnitt. Sensorer med denne vurderingen advarer om dyre tidsvinduer. Standard: 10%" - } - } + "description": "**Konfigurer terskler for prisvurderingsnivåer (lav/normal/høy) basert på sammenligning med etterfølgende 24-timers gjennomsnitt.**", + "data": { + "price_rating_threshold_low": "Lav-terskel", + "price_rating_threshold_high": "Høy-terskel" }, - "submit": "Fortsett →" + "data_description": { + "price_rating_threshold_low": "Prosentverdi for hvor mye gjeldende pris må være under det etterfølgende 24-timers gjennomsnittet for å kvalifisere som 'lav' vurdering. Eksempel: 5 betyr minst 5% under gjennomsnitt. Sensorer med denne vurderingen indikerer gunstige tidsvinduer. Standard: 5%", + "price_rating_threshold_high": "Prosentverdi for hvor mye gjeldende pris må være over det etterfølgende 24-timers gjennomsnittet for å kvalifisere som 'høy' vurdering. Eksempel: 10 betyr minst 10% over gjennomsnitt. Sensorer med denne vurderingen advarer om dyre tidsvinduer. Standard: 10%" + }, + "submit": "↩ Lagre & tilbake" }, "best_price": { "title": "💚 Beste Prisperiode Innstillinger", - "description": "_{step_progress}_\n\nKonfigurer innstillinger for **Beste Prisperiode** binærsensor. Denne sensoren er aktiv i perioder med de laveste strømprisene.\n\n---", + "description": "**Konfigurer innstillinger for Beste Prisperiode binærsensor. Denne sensoren er aktiv i perioder med de laveste strømprisene.**\n\n---", "sections": { "period_settings": { "name": "Periodeinnstillinger", @@ -219,11 +227,11 @@ } } }, - "submit": "Fortsett →" + "submit": "↩ Lagre & tilbake" }, "peak_price": { "title": "🔴 Toppprisperiode Innstillinger", - "description": "_{step_progress}_\n\nKonfigurer innstillinger for **Toppprisperiode** binærsensor. Denne sensoren er aktiv i perioder med de høyeste strømprisene.\n\n---", + "description": "**Konfigurer innstillinger for Toppprisperiode binærsensor. Denne sensoren er aktiv i perioder med de høyeste strømprisene.**\n\n---", "sections": { "period_settings": { "name": "Periodeinnstillinger", @@ -266,52 +274,48 @@ } } }, - "submit": "Fortsett →" + "submit": "↩ Lagre & tilbake" }, "price_trend": { "title": "📈 Pristrendterskler", - "description": "_{step_progress}_\n\n**Konfigurer terskler for pristrendsensorer. Disse sensorene sammenligner nåværende pris med gjennomsnittet av de neste N timene for å bestemme om prisene stiger, faller eller er stabile.**\n\n---", - "sections": { - "price_trend_thresholds": { - "name": "Pristrendterskler", - "description": "Definer pristrendnivåer.", - "data": { - "price_trend_threshold_rising": "Stigende terskel", - "price_trend_threshold_falling": "Fallende terskel" - }, - "data_description": { - "price_trend_threshold_rising": "Prosentverdi for gjennomsnittlig prisøkning per time som kvalifiserer trenden som 'stigende'. Eksempel: 5 betyr minst 5% økning per time. Sensorer med denne trenden indikerer at prisene vil stige raskt. Standard: 5%", - "price_trend_threshold_falling": "Prosentverdi for gjennomsnittlig prisnedgang per time som kvalifiserer trenden som 'synkende'. Eksempel: -5 betyr minst 5% nedgang per time. Sensorer med denne trenden indikerer at prisene vil synke raskt. Standard: -5%" - } - } + "description": "**Konfigurer terskler for pristrendsensorer. Disse sensorene sammenligner nåværende pris med gjennomsnittet av de neste N timene for å bestemme om prisene stiger, faller eller er stabile.**", + "data": { + "price_trend_threshold_rising": "Stigende terskel", + "price_trend_threshold_falling": "Fallende terskel" }, - "submit": "Fortsett →" + "data_description": { + "price_trend_threshold_rising": "Prosentverdi som gjennomsnittet av de neste N timene må være over den nåværende prisen for å kvalifisere som 'stigende' trend. Eksempel: 5 betyr gjennomsnittet er minst 5% høyere → prisene vil stige. Typiske verdier: 5-15%. Standard: 5%", + "price_trend_threshold_falling": "Prosentverdi (negativ) som gjennomsnittet av de neste N timene må være under den nåværende prisen for å kvalifisere som 'synkende' trend. Eksempel: -5 betyr gjennomsnittet er minst 5% lavere → prisene vil falle. Typiske verdier: -5 til -15%. Standard: -5%" + }, + "submit": "↩ Lagre & tilbake" }, "volatility": { "title": "💨 Volatilitets-terskler", - "description": "_{step_progress}_\n\n**Konfigurer terskler for volatilitetsklassifisering. Volatilitet måler relativ prisvariation ved hjelp av variasjonskoeffisienten (VK = standardavvik / gjennomsnitt × 100%). Disse tersklene er prosentverdier som fungerer på tvers av alle prisnivåer.**\n\nBrukes av:\n• Volatilitetssensorer (klassifisering)\n• Trendsensorer (adaptiv terskel justering: <moderat = mer følsom, ≥høy = mindre følsom)\n\n---", - "sections": { - "volatility_thresholds": { - "name": "Volatilitetsterskler", - "description": "Definer volatilitetsklassifiseringsnivåer.", - "data": { - "volatility_threshold_moderate": "Moderat terskel", - "volatility_threshold_high": "Høy terskel", - "volatility_threshold_very_high": "Veldig høy terskel" - }, - "data_description": { - "volatility_threshold_moderate": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'moderat'. Eksempel: 10 betyr standardavvik ≥ 10% av gjennomsnitt. Dette indikerer økt prisustabilitet. Standard: 10%", - "volatility_threshold_high": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'høy'. Eksempel: 20 betyr standardavvik ≥ 20% av gjennomsnitt. Dette indikerer betydelige prissvingninger. Standard: 20%", - "volatility_threshold_very_high": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'veldig høy'. Eksempel: 30 betyr standardavvik ≥ 30% av gjennomsnitt. Dette indikerer ekstrem prisustabilitet. Standard: 30%" - } - } + "description": "**Konfigurer terskler for volatilitetsklassifisering.** Volatilitet måler relativ prisvariation ved hjelp av variasjonskoeffisienten (VK = standardavvik / gjennomsnitt × 100%). Disse tersklene er prosentverdier som fungerer på tvers av alle prisnivåer.\n\nBrukes av:\n• Volatilitetssensorer (klassifisering)\n• Trendsensorer (adaptiv terskel justering: <moderat = mer følsom, ≥høy = mindre følsom)", + "data": { + "volatility_threshold_moderate": "Moderat terskel", + "volatility_threshold_high": "Høy terskel", + "volatility_threshold_very_high": "Veldig høy terskel" }, - "submit": "Fortsett →" + "data_description": { + "volatility_threshold_moderate": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'moderat'. Eksempel: 10 betyr standardavvik ≥ 10% av gjennomsnitt. Dette indikerer økt prisustabilitet. Standard: 10%", + "volatility_threshold_high": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'høy'. Eksempel: 20 betyr standardavvik ≥ 20% av gjennomsnitt. Dette indikerer betydelige prissvingninger. Standard: 20%", + "volatility_threshold_very_high": "Grenseverdi for standardavvik (% av gjennomsnitt) for å klassifisere prisvariasjonen som 'veldig høy'. Eksempel: 30 betyr standardavvik ≥ 30% av gjennomsnitt. Dette indikerer ekstrem prisustabilitet. Standard: 30%" + }, + "submit": "↩ Lagre & tilbake" }, "chart_data_export": { "title": "📊 Diagram-dataeksport Sensor", - "description": "_{step_progress}_\n\nDiagram-dataeksport-sensoren gir prisdata som sensorattributter.\n\n⚠️ **Merk:** Denne sensoren er en legacy-funksjon for kompatibilitet med eldre verktøy.\n\n**Anbefalt for nye oppsett:** Bruk `tibber_prices.get_chartdata` **tjenesten direkte** - den er mer fleksibel, effektiv og den moderne Home Assistant-tilnærmingen.\n\n**Når denne sensoren gir mening:**\n\n✅ Dashboardverktøyet ditt kan **kun** lese attributter (ingen tjenestekall)\n✅ Du trenger statiske data som oppdateres automatisk\n❌ **Ikke for automatiseringer:** Bruk `tibber_prices.get_chartdata` direkte der - mer fleksibel og effektiv!\n\n---\n\n**Aktiver sensoren:**\n\n1. Åpne **Innstillinger → Enheter og tjenester → Tibber Prices**\n2. Velg ditt hjem → Finn **'Diagramdataeksport'** (Diagnostikk-seksjonen)\n3. **Aktiver sensoren** (deaktivert som standard)\n\n**Konfigurasjon (valgfritt):**\n\nStandardinnstillinger fungerer umiddelbart (i dag+i morgen, 15-minutters intervaller, bare priser).\n\nFor tilpasning, legg til i **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle parametere:** Se `tibber_prices.get_chartdata` tjenestens dokumentasjon", - "submit": "Fullfør ✓" + "description": "Diagram-dataeksport-sensoren gir prisdata som sensorattributter.\n\n⚠️ **Merk:** Denne sensoren er en legacy-funksjon for kompatibilitet med eldre verktøy.\n\n**Anbefalt for nye oppsett:** Bruk `tibber_prices.get_chartdata` **tjenesten direkte** - den er mer fleksibel, effektiv og den moderne Home Assistant-tilnærmingen.\n\n**Når denne sensoren gir mening:**\n\n✅ Dashboardverktøyet ditt kan **kun** lese attributter (ingen tjenestekall)\n✅ Du trenger statiske data som oppdateres automatisk\n❌ **Ikke for automatiseringer:** Bruk `tibber_prices.get_chartdata` direkte der - mer fleksibel og effektiv!\n\n---\n\n**Aktiver sensoren:**\n\n1. Åpne **Innstillinger → Enheter og tjenester → Tibber Prices**\n2. Velg ditt hjem → Finn **'Diagramdataeksport'** (Diagnostikk-seksjonen)\n3. **Aktiver sensoren** (deaktivert som standard)\n\n**Konfigurasjon (valgfritt):**\n\nStandardinnstillinger fungerer umiddelbart (i dag+i morgen, 15-minutters intervaller, bare priser).\n\nFor tilpasning, legg til i **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle parametere:** Se `tibber_prices.get_chartdata` tjenestens dokumentasjon", + "submit": "↩ Ok & tilbake" + }, + "reset_to_defaults": { + "title": "🔄 Tilbakestill til standard", + "description": "⚠️ **Advarsel:** Dette vil tilbakestille **ALLE** innstillinger til fabrikkstandard.\n\n**Hva vil bli tilbakestilt:**\n• Alle prisvurderingsterskler\n• Alle volatilitetsterskler\n• Alle pristrendterskler\n• Alle innstillinger for beste prisperiode\n• Alle innstillinger for toppprisperiode\n• Visningsinnstillinger\n• Generelle innstillinger\n\n**Hva vil IKKE bli tilbakestilt:**\n• Ditt Tibber API-token\n• Valgt hjem\n• Valuta\n\n**💡 Tips:** Dette er nyttig hvis du vil starte på nytt etter å ha eksperimentert med innstillinger.", + "data": { + "confirm_reset": "Ja, tilbakestill alt til standard" + }, + "submit": "Tilbakestill nå" } }, "error": { @@ -339,7 +343,10 @@ "invalid_price_trend_falling": "Fallende trendgrense må være mellom -50% og -1%" }, "abort": { - "entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet." + "entry_not_found": "Tibber-konfigurasjonsoppføring ikke funnet.", + "reset_cancelled": "Tilbakestilling avbrutt. Ingen endringer ble gjort i konfigurasjonen din.", + "reset_successful": "✅ Alle innstillinger har blitt tilbakestilt til fabrikkstandard. Konfigurasjonen din er nå som en ny installasjon.", + "finished": "Konfigurasjon fullført." } }, "entity": { diff --git a/custom_components/tibber_prices/translations/nl.json b/custom_components/tibber_prices/translations/nl.json index 1782b3b..d078023 100644 --- a/custom_components/tibber_prices/translations/nl.json +++ b/custom_components/tibber_prices/translations/nl.json @@ -2,15 +2,15 @@ "config": { "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.", + "title": "Kies Account", + "description": "Je kunt een ander huis van een bestaand 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", + "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" @@ -29,7 +29,7 @@ "data": { "home_id": "Huis" }, - "title": "Kies een huis", + "title": "Kies een Huis", "submit": "Huis selecteren" }, "finish": { @@ -41,7 +41,7 @@ "submit": "Huis selecteren" }, "reauth_confirm": { - "title": "Tibber Prijs-integratie opnieuw authenticeren", + "title": "Tibber Price Integratie Opnieuw Authenticeren", "description": "Het toegangstoken voor Tibber is niet langer geldig. Voer een nieuw API-toegangstoken in om deze integratie te blijven gebruiken.\n\nOm een nieuw API-toegangstoken te genereren, bezoek https://developer.tibber.com.", "data": { "access_token": "API-toegangstoken" @@ -59,20 +59,20 @@ "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.", - "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." + "future_subscription_warning": "Let op: Het Tibber-contract van dit huis is nog niet gestart. De functionaliteit kan beperkt zijn totdat het contract actief wordt.", + "invalid_yaml_syntax": "Ongeldige YAML-syntaxis. Controleer inspringing, dubbele punten en speciale tekens.", + "invalid_yaml_structure": "YAML moet een dictionary/object zijn (sleutel: waarde paren), geen lijst of platte tekst.", + "service_call_failed": "Service call validatie mislukt: {error_detail}", + "missing_entry_id": "Entry-ID is verplicht maar werd niet opgegeven.", + "invalid_entry_id": "Ongeldige entry-ID of item niet gevonden.", + "missing_home_id": "Huis-ID ontbreekt in het configuratie-item.", + "user_data_not_available": "Gebruikersgegevens zijn niet beschikbaar. Ververs eerst de gebruikersgegevens.", + "price_fetch_failed": "Ophalen van prijsgegevens mislukt. Controleer de logs voor details." }, "abort": { "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.", - "setup_complete": "Installatie voltooid! Je kunt extra opties voor Tibber Prices wijzigen in de integratie-opties na het sluiten van deze dialoog.", + "setup_complete": "Installatie voltooid! Je kunt extra opties voor Tibber Prices wijzigen in de integratie-opties na het sluiten van dit dialoogvenster.", "reauth_successful": "Herauthenticatie geslaagd. De integratie is bijgewerkt met het nieuwe toegangstoken." } }, @@ -83,47 +83,47 @@ "home": { "entry_type": "Tijdreisweergave", "initiate_flow": { - "user": "Tijdreisweergave maken" + "user": "Tijdreisweergave Aanmaken" }, - "title": "Tijdreisweergave maken", + "title": "Tijdreisweergave Aanmaken", "step": { "user": { - "title": "Configuratie-item 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.", + "title": "Selecteer Configuratie-Item", + "description": "Selecteer het configuratie-item waarvoor je een tijdreisweergave wilt aanmaken.\n\n**Tijdreisweergaven** laten je historische prijsgegevens zien alsof het de huidige tijd is. Dit is handig voor het testen van automatiseringen of het analyseren van prijspatronen uit het verleden.", "data": { - "parent_entry_id": "Configuratie-item" + "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", + "title": "Configureer Tijdverschuiving", + "description": "Configureer hoe ver terug in de tijd deze weergave moet reizen.\n\n**Aanbevolen:** Gebruik **≥2 dagen** verschuiving om conflicten met \"gisteren\"-entiteiten te voorkomen die ook historische gegevens bieden.\n\n**Voorbeelden:**\n• **-7 dagen**: Bekijk prijzen van 7 dagen geleden\n• **-2 dagen, 3 uur**: Bekijk prijzen van 2 dagen en 3 uur geleden\n• **-14 dagen**: Bekijk prijzen van 2 weken geleden", "data": { - "virtual_time_offset_days": "Dagen terug", - "time_offset": "Extra tijdverschuiving" + "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.", + "virtual_time_offset_days": "Hoeveel dagen terug in de tijd reizen. Schuifbereik: 0 tot 374 dagen (≈1 jaar). Aanbevolen: ≥2 dagen om conflicten met \"gisteren\"-entiteiten te voorkomen.", "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", + "title": "Tijdverschuiving Opnieuw Configureren", "description": "Werk de tijdverschuiving voor deze tijdreisweergave bij.", "data": { - "virtual_time_offset_days": "Dagen terug", - "time_offset": "Extra tijdverschuiving" + "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.", + "virtual_time_offset_days": "Hoeveel dagen terug in de tijd reizen. Schuifbereik: 0 tot 374 dagen (≈1 jaar). Aanbevolen: ≥2 dagen om conflicten met \"gisteren\"-entiteiten te voorkomen.", "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": { - "no_time_offset": "Ten minste één tijdverschuivingswaarde moet negatief zijn (alleen historische gegevens)." + "no_time_offset": "Minimaal één tijdverschuivingswaarde moet negatief zijn (alleen historische gegevens)." }, "abort": { - "already_configured": "**Een tijdreis-weergave met deze exacte tijdverschuiving bestaat al.**\n\nKies een andere verschuiving.", + "already_configured": "**Een tijdreisweergave met exact deze tijdverschuiving bestaat al.**\n\nKies een andere verschuiving.", "no_main_entries": "Geen hoofdconfiguratie-items gevonden. Voeg eerst een Tibber-huis toe.", "parent_entry_not_found": "Geselecteerd configuratie-item niet gevonden." } @@ -132,336 +132,343 @@ "options": { "step": { "init": { - "title": "⚙️ Algemene instellingen", - "description": "_{step_progress}_\n\n**Configureer algemene instellingen voor Tibber-prijsinformatie en -beoordelingen.**\n\n---\n\n**Gebruiker:** {user_login}", + "menu_options": { + "general_settings": "⚙️ Algemene Instellingen", + "display_settings": "💱 Valuta Weergave", + "current_interval_price_rating": "📊 Prijsbeoordeling", + "volatility": "💨 Prijsvolatiliteit", + "best_price": "💚 Beste Prijs Periode", + "peak_price": "🔴 Piekprijs Periode", + "price_trend": "📈 Prijstrend", + "chart_data_export": "📊 Grafiekdata Export Sensor", + "reset_to_defaults": "🔄 Standaardwaarden Herstellen", + "finish": "⬅️ Terug" + } + }, + "general_settings": { + "title": "⚙️ Algemene Instellingen", + "description": "**Configureer algemene instellingen voor Tibber Prijsinformatie & Beoordelingen.**\n\n---\n\n**Gebruiker:** {user_login}", "data": { - "extended_descriptions": "Uitgebreide beschrijvingen", - "average_sensor_display": "Gemiddelde sensor weergave" + "extended_descriptions": "Uitgebreide Beschrijvingen", + "average_sensor_display": "Gemiddelde Sensor Weergave" }, "data_description": { "extended_descriptions": "Bepaalt of entiteitsattributen gedetailleerde uitleg en gebruikstips bevatten.\n\n• Uitgeschakeld (standaard): Alleen korte beschrijving\n• Ingeschakeld: Gedetailleerde uitleg + praktische gebruiksvoorbeelden\n\nVoorbeeld:\nUitgeschakeld = 1 attribuut\nIngeschakeld = 2 extra attributen", - "average_sensor_display": "Kies welke statistische maat wordt weergegeven in de sensorstatus voor gemiddelde prijssensoren. De andere waarde wordt weergegeven als attribuut. Mediaan is resistenter tegen extreme waarden, terwijl het rekenkundig gemiddelde het traditionele gemiddelde vertegenwoordigt. Standaard: Mediaan" + "average_sensor_display": "Kies welke statistische maat weergegeven moet worden in de sensorstatus voor gemiddelde prijssensoren. De andere waarde wordt als attribuut getoond. Mediaan is resistenter tegen extreme waarden, terwijl rekenkundig gemiddelde het traditionele gemiddelde vertegenwoordigt. Standaard: Mediaan" }, - "submit": "Doorgaan →" + "submit": "↩ Opslaan & Terug" }, "display_settings": { - "title": "💱 Valuta-weergave-instellingen", - "description": "_{step_progress}_\n\n**Configureer hoe elektriciteitsprijzen worden weergegeven - in basisvaluta (€, kr) of subeenheid (ct, øre).**\n\n---", + "title": "💱 Valuta Weergave-Instellingen", + "description": "**Configureer hoe elektriciteitsprijzen worden weergegeven - in basisvaluta (€, kr) of subeenheid (ct, øre).**\n\n---", "data": { "currency_display_mode": "Weergavemodus" }, "data_description": { - "currency_display_mode": "Kies hoe prijzen worden weergegeven:\n\n• **Basisvaluta** (€/kWh, kr/kWh): Decimaalwaarden (bijv. 0,25 €/kWh) - verschillen zichtbaar vanaf 3e-4e decimaal\n• **Subeenheid** (ct/kWh, øre/kWh): Grotere waarden (bijv. 25,00 ct/kWh) - verschillen al zichtbaar vanaf 1e decimaal\n\nStandaard hangt af van jouw valuta:\n• EUR → Subeenheid (cent) - Duitse/Nederlandse voorkeur\n• NOK/SEK/DKK → Basisvaluta (kronen) - Scandinavische voorkeur\n• USD/GBP → Basisvaluta\n\n**💡 Tip:** Bij keuze van subeenheid kun je de extra sensor \"Huidige elektriciteitsprijs (Energie-dashboard)\" activeren (standaard uitgeschakeld)." + "currency_display_mode": "Kies hoe prijzen worden weergegeven:\n\n• **Basisvaluta** (€/kWh, kr/kWh): Decimale waarden (bijv. 0,25 €/kWh) - verschillen zichtbaar vanaf 3e-4e decimaal\n• **Subeenheid Valuta** (ct/kWh, øre/kWh): Grotere waarden (bijv. 25,00 ct/kWh) - verschillen zichtbaar vanaf 1e decimaal\n\nStandaard hangt af van je valuta:\n• EUR → Subeenheid (cents) - Duitse/Nederlandse voorkeur\n• NOK/SEK/DKK → Basis (kronen) - Scandinavische voorkeur\n• USD/GBP → Basisvaluta\n\n**💡 Tip:** Bij selectie van Subeenheid Valuta kun je de extra \"Huidige Elektriciteitsprijs (Energie Dashboard)\" sensor inschakelen (standaard uitgeschakeld)." }, - "submit": "Doorgaan →" + "submit": "↩ Opslaan & Terug" }, "current_interval_price_rating": { - "title": "📊 Prijsbeoordelingsdrempels", - "description": "_{step_progress}_\n\n**Configureer drempels voor prijsbeoordelingsniveaus (laag/normaal/hoog) op basis van vergelijking met het lopende 24-uurs gemiddelde.**\n\n---", - "sections": { - "price_rating_thresholds": { - "name": "Prijsbeoordelingsdrempels", - "description": "Definieer prijsbeoordelingsniveaus.", - "data": { - "price_rating_threshold_low": "Lage drempel", - "price_rating_threshold_high": "Hoge drempel" - }, - "data_description": { - "price_rating_threshold_low": "Percentage onder het lopende 24-uurs gemiddelde dat de huidige prijs moet zijn om als 'laag' te kwalificeren. Voorbeeld: 5 betekent minstens 5% onder gemiddelde. Sensoren met deze beoordeling geven gunstige tijdsvensters aan. Standaard: 5%", - "price_rating_threshold_high": "Percentage boven het lopende 24-uurs gemiddelde dat de huidige prijs moet zijn om als 'hoog' te kwalificeren. Voorbeeld: 10 betekent minstens 10% boven gemiddelde. Sensoren met deze beoordeling waarschuwen voor dure tijdsvensters. Standaard: 10%" - } - } + "title": "📊 Prijsbeoordeling Drempelwaarden", + "description": "**Configureer drempelwaarden voor prijsbeoordelingsniveaus (laag/normaal/hoog) gebaseerd op vergelijking met het voortschrijdende 24-uurs gemiddelde.**", + "data": { + "price_rating_threshold_low": "Lage Drempel", + "price_rating_threshold_high": "Hoge Drempel" }, - "submit": "Doorgaan →" + "data_description": { + "price_rating_threshold_low": "Percentage onder het voortschrijdende 24-uurs gemiddelde dat de huidige prijs moet zijn om te kwalificeren als 'laag' beoordelingsniveau. Voorbeeld: 5 betekent minimaal 5% onder gemiddelde. Sensoren met deze beoordeling geven gunstige tijdvensters aan. Standaard: 5%", + "price_rating_threshold_high": "Percentage boven het voortschrijdende 24-uurs gemiddelde dat de huidige prijs moet zijn om te kwalificeren als 'hoog' beoordelingsniveau. Voorbeeld: 10 betekent minimaal 10% boven gemiddelde. Sensoren met deze beoordeling waarschuwen voor dure tijdvensters. Standaard: 10%" + }, + "submit": "↩ Opslaan & Terug" }, "best_price": { - "title": "💚 Beste Prijs Periode Instellingen", - "description": "_{step_progress}_\n\nConfigureer instellingen voor de **Beste Prijs Periode** binaire sensor. Deze sensor is actief tijdens periodes met de laagste elektriciteitsprijzen.\n\n---", + "title": "💚 Best Price Period Settings", + "description": "**Configure settings for the Best Price Period binary sensor. This sensor is active during periods with the lowest electricity prices.**\n\n---", "sections": { "period_settings": { - "name": "Periode-instellingen", - "description": "Configureer periodelengte en prijsniveaubeperkingen.", + "name": "Period Duration & Levels", + "description": "Configure how long periods should be and which price levels to include.", "data": { - "best_price_min_period_length": "Minimale periodelengte", - "best_price_max_level": "Prijsniveaufilter", - "best_price_max_level_gap_count": "Gaptolerantie" + "best_price_min_period_length": "Minimum Period Length", + "best_price_max_level": "Price Level Filter", + "best_price_max_level_gap_count": "Gap Tolerance" }, "data_description": { - "best_price_min_period_length": "Minimale duur voordat een periode als 'beste prijs' wordt beschouwd. Langere periodes zijn praktischer voor apparaten zoals vaatwassers of warmtepompen. Beste prijs periodes vereisen minimaal 60 minuten (versus 30 minuten voor piekprijs waarschuwingen) omdat ze betekenisvolle tijdvensters voor verbruiksplanning moeten bieden, niet alleen korte kansen.", - "best_price_max_level": "Toon alleen beste prijs periodes als ze intervallen bevatten met prijsniveaus ≤ geselecteerde waarde. Bijvoorbeeld: selecteren van '**Goedkoop**' betekent dat de periode minstens één '**Zeer goedkoop**' of '**Goedkoop**' interval moet hebben. Dit zorgt ervoor dat 'beste prijs'-periodes niet alleen relatief goedkoop zijn voor de dag, maar daadwerkelijk goedkoop in absolute termen. Selecteer '**Alle**' om beste prijzen te tonen ongeacht hun absolute prijsniveau.", - "best_price_max_level_gap_count": "Maximum aantal opeenvolgende intervallen dat precies één niveaustap mag afwijken van het vereiste niveau. Bijvoorbeeld: met '**Goedkoop**' filter en gaptelling 1 wordt de reeks '**Goedkoop**, **Goedkoop**, **Normaal**, **Goedkoop**' geaccepteerd (**Normaal** is één stap boven **Goedkoop**). Dit voorkomt dat periodes worden opgesplitst door incidentele niveauafwijkingen. **Let op:** Gaptolerantie vereist periodes ≥90 minuten (6 intervallen) om afwijkingen effectief te detecteren. Standaard: 0 (strikte filtering, geen tolerantie)." + "best_price_min_period_length": "Minimum duration for a period to be considered as 'best price'. Longer periods are more practical for running appliances like dishwashers or heat pumps. Best price periods require 60 minutes minimum (vs. 30 minutes for peak price warnings) because they should provide meaningful time windows for consumption planning, not just brief opportunities.", + "best_price_max_level": "Only show best price periods if they contain intervals with price levels ≤ selected value. For example, selecting '**Cheap**' means the period must have at least one '**Very cheap**' or '**Cheap**' interval. This ensures 'best price' periods are not just relatively cheap for the day, but actually cheap in absolute terms. Select '**Any**' to show best prices regardless of their absolute price level.", + "best_price_max_level_gap_count": "Maximaal aantal opeenvolgende intervallen toegestaan die precies één niveaustap afwijken van het vereiste niveau. Bijvoorbeeld: met '**Goedkoop**' filter en gat telling 1, wordt een reeks '**Goedkoop**, **Goedkoop**, **Normaal**, **Goedkoop**' geaccepteerd (**Normaal** is één stap boven **Goedkoop**). Dit voorkomt dat periodes worden gesplitst door incidentele niveauafwijkingen. **Let op:** Gat tolerantie vereist periodes ≥90 minuten (6 intervallen) om uitschieters effectief te detecteren. Standaard: 0 (strikte filtering, geen tolerantie)." } }, "flexibility_settings": { - "name": "Flexibiliteitsinstellingen", - "description": "Configureer prijsvergelijkingsdrempels en filtering.", + "name": "Flexibiliteit & Drempelwaarden", + "description": "Bepaal hoeveel prijzen kunnen afwijken en nog steeds als 'beste prijs' kwalificeren.", "data": { "best_price_flex": "Flexibiliteit", - "best_price_min_distance_from_avg": "Minimale afstand" + "best_price_min_distance_from_avg": "Minimale Afstand" }, "data_description": { - "best_price_flex": "Maximaal boven de dagelijkse minimumprijs waarbij intervallen nog kwalificeren als 'beste prijs'. Aanbevolen: 15-20 met versoepeling geactiveerd (standaard), of 25-35 zonder versoepeling. Maximum: 50 (harde limiet voor betrouwbare periodedetectie).", - "best_price_min_distance_from_avg": "Zorgt ervoor dat periodes significant goedkoper zijn dan het daggemiddelde, niet slechts marginaal eronder. Dit filtert ruis en voorkomt dat licht-onder-gemiddelde periodes op dagen met vlakke prijzen als 'beste prijs' worden gemarkeerd. Hogere waarden = striktere filtering (alleen echt goedkope periodes kwalificeren). Standaard: 5 betekent dat periodes minimaal 5% onder het daggemiddelde moeten liggen." + "best_price_flex": "Maximum boven de dagelijkse minimumprijs dat intervallen kunnen zijn en nog steeds als 'beste prijs' kwalificeren. Aanbevolen: 15-20 met relaxatie ingeschakeld (standaard), of 25-35 zonder relaxatie. Maximum: 50 (harde limiet voor betrouwbare periodedetectie).", + "best_price_min_distance_from_avg": "Zorgt ervoor dat periodes significant goedkoper zijn dan het dagelijkse gemiddelde, niet slechts marginaal eronder. Dit filtert ruis en voorkomt het markeren van licht-onder-gemiddelde periodes als 'beste prijs' op dagen met vlakke prijzen. Hogere waarden = striktere filtering (alleen echt goedkope periodes kwalificeren). Standaard: 5 betekent dat periodes minimaal 5% onder het dagelijkse gemiddelde moeten zijn." } }, "relaxation_and_target_periods": { - "name": "Versoepeling & Doelperiodes", - "description": "Configureer automatische filterversoepeling en doelperiodetellingen. Activeer 'Minimum aantal bereiken' om versoepeling te activeren.", + "name": "Relaxatie & Doelperiodes", + "description": "Configureer automatische filterrelaxatie en doel periode aantallen. Schakel 'Bereik Minimum Aantal' in om relaxatie te activeren.", "data": { - "enable_min_periods_best": "Minimum aantal bereiken", - "min_periods_best": "Minimum periodes", - "relaxation_attempts_best": "Versoepeling pogingen" + "enable_min_periods_best": "Bereik Minimum Aantal", + "min_periods_best": "Minimale Periodes", + "relaxation_attempts_best": "Relaxatie Pogingen" }, "data_description": { - "enable_min_periods_best": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken, wat minder optimale tijdvensters kan omvatten als beste-prijs periodes.", - "min_periods_best": "Minimum aantal beste prijs periodes om per dag na te streven. Filters worden stap voor stap versoepeld om deze telling te bereiken. Alleen actief wanneer 'Minimum aantal bereiken' is ingeschakeld. Standaard: 1", - "relaxation_attempts_best": "Hoeveel flexniveaus (pogingen) te proberen voordat wordt opgegeven. Elke poging voert alle filtercombinaties uit op het nieuwe flexniveau. Meer pogingen vergroten de kans op het vinden van extra periodes ten koste van langere verwerkingstijd." + "enable_min_periods_best": "Wanneer ingeschakeld, worden filters geleidelijk versoepeld als niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken, wat minder optimale tijdvensters als beste-prijs periodes kan bevatten.", + "min_periods_best": "Minimaal aantal beste prijsperiodes om per dag na te streven. Filters worden stapsgewijs versoepeld om dit aantal te proberen te bereiken. Alleen actief wanneer 'Bereik Minimum Aantal' is ingeschakeld. Standaard: 1", + "relaxation_attempts_best": "Hoeveel flex niveaus (pogingen) te proberen voordat opgegeven wordt. Elke poging voert alle filtercombinaties uit op het nieuwe flex niveau. Meer pogingen verhogen de kans om extra periodes te vinden ten koste van langere verwerkingstijd." } } }, - "submit": "Doorgaan →" + "submit": "↩ Opslaan & Terug" }, "peak_price": { "title": "🔴 Piekprijs Periode Instellingen", - "description": "_{step_progress}_\n\nConfigureer instellingen voor de **Piekprijs Periode** binaire sensor. Deze sensor is actief tijdens periodes met de hoogste elektriciteitsprijzen.\n\n---", + "description": "**Configureer instellingen voor de Piekprijs Periode binaire sensor. Deze sensor is actief tijdens periodes met de hoogste elektriciteitsprijzen.**\n\n---", "sections": { "period_settings": { - "name": "Periode-instellingen", - "description": "Configureer periodelengte en prijsniveaubeperkingen.", + "name": "Periode Instellingen", + "description": "Configureer periodeduur en prijsniveau beperkingen.", "data": { - "peak_price_min_period_length": "Minimale periodelengte", - "peak_price_min_level": "Prijsniveaufilter", - "peak_price_max_level_gap_count": "Gaptolerantie" + "peak_price_min_period_length": "Minimale Periode Lengte", + "peak_price_min_level": "Prijsniveau Filter", + "peak_price_max_level_gap_count": "Gat Tolerantie" }, "data_description": { - "peak_price_min_period_length": "Minimale duur voordat een periode als 'piekprijs' wordt beschouwd. Piekprijs waarschuwingen zijn toegestaan voor kortere periodes (minimaal 30 minuten versus 60 minuten voor beste prijs) omdat korte dure pieken de moeite waard zijn om voor te waarschuwen, zelfs als ze te kort zijn voor verbruiksplanning.", - "peak_price_min_level": "Toon alleen piekprijs periodes als ze intervallen bevatten met prijsniveaus ≥ geselecteerde waarde. Bijvoorbeeld: selecteren van '**Duur**' betekent dat de periode minstens één '**Duur**' of '**Zeer duur**' interval moet hebben. Dit zorgt ervoor dat 'piekprijs'-periodes niet alleen relatief duur zijn voor de dag, maar daadwerkelijk duur in absolute termen. Selecteer '**Alle**' om piekprijzen te tonen ongeacht hun absolute prijsniveau.", - "peak_price_max_level_gap_count": "Maximum aantal opeenvolgende intervallen dat precies één niveaustap mag afwijken van het vereiste niveau. Bijvoorbeeld: met '**Duur**' filter en gaptelling 1 wordt de reeks '**Duur**, **Duur**, **Normaal**, **Duur**' geaccepteerd (**Normaal** is één stap onder **Duur**). Dit voorkomt dat periodes worden opgesplitst door incidentele niveauafwijkingen. **Let op:** Gaptolerantie vereist periodes ≥90 minuten (6 intervallen) om afwijkingen effectief te detecteren. Standaard: 0 (strikte filtering, geen tolerantie)." + "peak_price_min_period_length": "Minimale duur voor een periode om als 'piekprijs' te worden beschouwd. Piekprijs waarschuwingen zijn toegestaan voor kortere periodes (30 minuten minimum vs. 60 minuten voor beste prijs) omdat korte dure pieken de moeite waard zijn om voor te waarschuwen, zelfs als ze te kort zijn voor verbruiksplanning.", + "peak_price_min_level": "Toon alleen piekprijs periodes als ze intervallen bevatten met prijsniveaus ≥ geselecteerde waarde. Bijvoorbeeld, bij selectie '**Duur**' moet de periode minimaal één '**Duur**' of '**Zeer duur**' interval hebben. Dit zorgt ervoor dat 'piekprijs' periodes niet alleen relatief duur zijn voor de dag, maar daadwerkelijk duur in absolute termen. Selecteer '**Alles**' om piekprijzen te tonen ongeacht hun absolute prijsniveau.", + "peak_price_max_level_gap_count": "Maximaal aantal opeenvolgende intervallen toegestaan die precies één niveaustap afwijken van het vereiste niveau. Bijvoorbeeld: met '**Duur**' filter en gat telling 2, wordt een reeks '**Duur**, **Normaal**, **Normaal**, **Duur**' geaccepteerd (**Normaal** is één stap onder **Duur**). Dit voorkomt dat periodes worden gesplitst door incidentele niveauafwijkingen. **Let op:** Gat tolerantie vereist periodes ≥90 minuten (6 intervallen) om uitschieters effectief te detecteren. Standaard: 0 (strikte filtering, geen tolerantie)." } }, "flexibility_settings": { - "name": "Flexibiliteitsinstellingen", - "description": "Configureer prijsvergelijkingscriteria en filtering.", + "name": "Flexibiliteit Instellingen", + "description": "Configureer drempelwaarden voor prijsvergelijking en filtering.", "data": { "peak_price_flex": "Flexibiliteit", - "peak_price_min_distance_from_avg": "Minimale afstand" + "peak_price_min_distance_from_avg": "Minimale Afstand" }, "data_description": { - "peak_price_flex": "Maximaal onder de dagelijkse maximumprijs waarbij intervallen nog kwalificeren als 'piekprijs'. Aanbeveling: -15 tot -20 met versoepeling geactiveerd (standaard), of -25 tot -35 zonder versoepeling. Maximum: -50 (harde limiet voor betrouwbare periodedetectie). Let op: Negatieve waarden geven de afstand onder het maximum aan.", - "peak_price_min_distance_from_avg": "Zorgt ervoor dat periodes significant duurder zijn dan het daggemiddelde, niet slechts marginaal erboven. Dit filtert ruis en voorkomt dat licht-boven-gemiddelde periodes op dagen met vlakke prijzen als 'piekprijs' worden gemarkeerd. Hogere waarden = striktere filtering (alleen echt dure periodes kwalificeren). Standaard: 5 betekent dat periodes minimaal 5% boven het daggemiddelde moeten liggen." + "peak_price_flex": "Maximum onder de dagelijkse maximumprijs dat intervallen kunnen zijn en nog steeds als 'piekprijs' kwalificeren. Aanbevolen: -15 tot -20 met relaxatie ingeschakeld (standaard), of -25 tot -35 zonder relaxatie. Maximum: -50 (harde limiet voor betrouwbare periodedetectie). Let op: Negatieve waarden geven afstand onder maximum aan.", + "peak_price_min_distance_from_avg": "Zorgt ervoor dat periodes significant duurder zijn dan het dagelijkse gemiddelde, niet slechts marginaal erboven. Dit filtert ruis en voorkomt het markeren van licht-boven-gemiddelde periodes als 'piekprijs' op dagen met vlakke prijzen. Hogere waarden = striktere filtering (alleen echt dure periodes kwalificeren). Standaard: 5 betekent dat periodes minimaal 5% boven het dagelijkse gemiddelde moeten zijn." } }, "relaxation_and_target_periods": { - "name": "Ontspanning en doelperiodes", - "description": "Configureer automatische filterontspanning en doelperiode-aantallen. Schakel 'Probeer minimum aantal periodes te bereiken' in om ontspanning te activeren.", + "name": "Relaxatie & Doelperiodes", + "description": "Configureer automatische filterrelaxatie en doel periode aantallen. Schakel 'Bereik Minimum Aantal' in om relaxatie te activeren.", "data": { - "enable_min_periods_peak": "Probeer minimum aantal periodes te bereiken", - "min_periods_peak": "Minimum aantal periodes", - "relaxation_attempts_peak": "Aantal ontspanningspogingen" + "enable_min_periods_peak": "Bereik Minimum Aantal", + "min_periods_peak": "Minimale Periodes", + "relaxation_attempts_peak": "Relaxatie Pogingen" }, "data_description": { - "enable_min_periods_peak": "Wanneer ingeschakeld worden filters geleidelijk versoepeld als er niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om ervoor te zorgen dat je wordt gewaarschuwd voor dure periodes, zelfs op dagen met ongebruikelijke prijspatronen.", - "min_periods_peak": "Minimum aantal piekprijsperiodes om naar te streven per dag. Filters worden stap voor stap versoepeld om dit aantal te proberen bereiken. Alleen actief wanneer 'Probeer minimum aantal periodes te bereiken' is ingeschakeld. Standaard: 1", - "relaxation_attempts_peak": "Hoeveel keer de ontspanningslogica filters opnieuw mag proberen. Gebruik meer pogingen wanneer de piekperiodes moeilijk te vinden zijn door vlakke of zeer grillige dagen. Elke extra poging kost wat extra verwerkingstijd maar vergroot de kans dat periodes worden gevonden." + "enable_min_periods_peak": "Wanneer ingeschakeld, worden filters geleidelijk versoepeld als niet genoeg periodes worden gevonden. Dit probeert het gewenste minimum aantal periodes te bereiken om je te waarschuwen voor dure periodes, zelfs op dagen met ongebruikelijke prijspatronen.", + "min_periods_peak": "Minimaal aantal piekprijs periodes om per dag na te streven. Filters worden stapsgewijs versoepeld om dit aantal te proberen te bereiken. Alleen actief wanneer 'Bereik Minimum Aantal' is ingeschakeld. Standaard: 1", + "relaxation_attempts_peak": "Hoeveel flex niveaus (pogingen) te proberen voordat opgegeven wordt. Elke poging voert alle filtercombinaties uit op het nieuwe flex niveau. Meer pogingen verhogen de kans om extra piekperiodes te vinden ten koste van langere verwerkingstijd." } } }, - "submit": "Doorgaan →" + "submit": "↩ Opslaan & Terug" }, "price_trend": { - "title": "📈 Prijstrenddrempels", - "description": "_{step_progress}_\n\n**Configureer drempels voor prijstrendsensoren. Deze sensoren vergelijken de huidige prijs met het gemiddelde van de volgende N uur om te bepalen of prijzen stijgen, dalen of stabiel zijn.**\n\n---", - "sections": { - "price_trend_thresholds": { - "name": "Prijstrenddrempels", - "description": "Definieer prijstrendniveaus.", - "data": { - "price_trend_threshold_rising": "Stijgende drempel", - "price_trend_threshold_falling": "Dalende drempel" - }, - "data_description": { - "price_trend_threshold_rising": "Percentage voor gemiddelde prijsstijging per uur die de trend kwalificeert als 'stijgend'. Voorbeeld: 5 betekent minstens 5% stijging per uur. Sensoren met deze trend geven aan dat prijzen snel zullen stijgen. Standaard: 5%", - "price_trend_threshold_falling": "Percentage voor gemiddelde prijsdaling per uur die de trend kwalificeert als 'dalend'. Voorbeeld: -5 betekent minstens 5% daling per uur. Sensoren met deze trend geven aan dat prijzen snel zullen dalen. Standaard: -5%" - } - } + "title": "📈 Prijstrend Drempelwaarden", + "description": "**Configureer drempelwaarden voor prijstrend sensoren. Deze sensoren vergelijken de huidige prijs met het gemiddelde van de volgende N uur om te bepalen of prijzen stijgen, dalen of stabiel zijn.**", + "data": { + "price_trend_threshold_rising": "Stijgende Drempel", + "price_trend_threshold_falling": "Dalende Drempel" }, - "submit": "Doorgaan →" + "data_description": { + "price_trend_threshold_rising": "Percentage dat het gemiddelde van de volgende N uur boven de huidige prijs moet zijn om te kwalificeren als 'stijgende' trend. Voorbeeld: 5 betekent dat het gemiddelde minimaal 5% hoger is → prijzen zullen stijgen. Typische waarden: 5-15%. Standaard: 5%", + "price_trend_threshold_falling": "Percentage (negatief) dat het gemiddelde van de volgende N uur onder de huidige prijs moet zijn om te kwalificeren als 'dalende' trend. Voorbeeld: -5 betekent dat het gemiddelde minimaal 5% lager is → prijzen zullen dalen. Typische waarden: -5 tot -15%. Standaard: -5%" + }, + "submit": "↩ Opslaan & Terug" }, "volatility": { - "title": "💨 Volatiliteit Drempels", - "description": "_{step_progress}_\n\n**Configureer drempels voor volatiliteitsclassificatie. Volatiliteit meet relatieve prijsvariatie met behulp van de variatiecoëfficiënt (VC = standaarddeviatie / gemiddelde × 100%). Deze drempels zijn percentagewaarden die werken over alle prijsniveaus.**\n\nGebruikt door:\n• Volatiliteitssensoren (classificatie)\n• Trendsensoren (adaptieve drempelaanpassing: <matig = gevoeliger, ≥hoog = minder gevoelig)\n\n---", - "sections": { - "volatility_thresholds": { - "name": "Volatiliteitdrempels", - "description": "Definieer volatiliteitsclassificatieniveaus.", - "data": { - "volatility_threshold_moderate": "Matige drempel", - "volatility_threshold_high": "Hoge drempel", - "volatility_threshold_very_high": "Zeer hoge drempel" - }, - "data_description": { - "volatility_threshold_moderate": "Grenswaarde voor standaardafwijking (% van gemiddelde) om prijsvariatie als 'matig' te classificeren. Voorbeeld: 10 betekent standaardafwijking ≥ 10% van gemiddelde. Dit wijst op verhoogde prijsinstabiliteit. Standaard: 10%", - "volatility_threshold_high": "Grenswaarde voor standaardafwijking (% van gemiddelde) om prijsvariatie als 'hoog' te classificeren. Voorbeeld: 20 betekent standaardafwijking ≥ 20% van gemiddelde. Dit wijst op aanzienlijke prijsschommelingen. Standaard: 20%", - "volatility_threshold_very_high": "Grenswaarde voor standaardafwijking (% van gemiddelde) om prijsvariatie als 'zeer hoog' te classificeren. Voorbeeld: 30 betekent standaardafwijking ≥ 30% van gemiddelde. Dit wijst op extreme prijsinstabiliteit. Standaard: 30%" - } - } + "title": "💨 Prijsvolatiliteit Drempelwaarden", + "description": "**Configureer drempelwaarden voor volatiliteitsclassificatie.** Volatiliteit meet relatieve prijsvariatie met de variëfficcïnt (CV = standaarddeviatie / gemiddelde × 100%). Deze drempelwaarden zijn percentagewaarden die werken over alle prijsniveaus.\n\nGebruikt door:\n• Volatiliteit sensoren (classificatie)\n• Trend sensoren (adaptieve drempelaanpassing: <gematigd = gevoeliger, ≥hoog = minder gevoelig)", + "data": { + "volatility_threshold_moderate": "Gematigde Drempel", + "volatility_threshold_high": "Hoge Drempel", + "volatility_threshold_very_high": "Zeer Hoge Drempel" }, - "submit": "Doorgaan →" + "data_description": { + "volatility_threshold_moderate": "Variëfficcïnt (CV) waarbij prijzen als 'gematigd volatiel' worden beschouwd. CV = (standaarddeviatie / gemiddelde) × 100%. Voorbeeld: 15 betekent prijsschommelingen van ±15% rond gemiddelde. Sensoren tonen deze classificatie, trend sensoren worden gevoeliger. Standaard: 15%", + "volatility_threshold_high": "Variëfficcïnt (CV) waarbij prijzen als 'hoog volatiel' worden beschouwd. Voorbeeld: 30 betekent prijsschommelingen van ±30% rond gemiddelde. Grotere prijssprongen verwacht, trend sensoren worden minder gevoelig. Standaard: 30%", + "volatility_threshold_very_high": "Variëfficcïnt (CV) waarbij prijzen als 'zeer hoog volatiel' worden beschouwd. Voorbeeld: 50 betekent extreme prijsschommelingen van ±50% rond gemiddelde. Op zulke dagen zijn sterke prijspieken waarschijnlijk. Standaard: 50%" + }, + "submit": "↩ Opslaan & Terug" }, "chart_data_export": { - "title": "📊 Grafiek-data-export Sensor", - "description": "_{step_progress}_\n\nDe Grafiek-data-export sensor biedt prijsgegevens als sensorattributen.\n\n⚠️ **Let op:** Deze sensor is een legacy-functie voor compatibiliteit met oudere tools.\n\n**Aanbevolen voor nieuwe setups:** Gebruik de `tibber_prices.get_chartdata` **service direct** - deze is flexibeler, efficiënter en de moderne Home Assistant-aanpak.\n\n**Wanneer deze sensor zinvol is:**\n\n✅ Je dashboardtool kan **alleen** attributen lezen (geen service-aanroepen)\n✅ Je hebt statische data nodig die automatisch bijwerkt\n❌ **Niet voor automatiseringen:** Gebruik `tibber_prices.get_chartdata` daar direct - flexibeler en efficiënter!\n\n---\n\n**Activeer de sensor:**\n\n1. Open **Instellingen → Apparaten en diensten → Tibber Prices**\n2. Selecteer je thuis → Vind **'Grafiekgegevensexport'** (Diagnostiek-sectie)\n3. **Activeer de sensor** (standaard uitgeschakeld)\n\n**Configuratie (optioneel):**\n\nStandaardinstellingen werken direct (vandaag+morgen, 15-minuten intervallen, alleen prijzen).\n\nVoor aanpassing, voeg toe aan **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle parameters:** Zie `tibber_prices.get_chartdata` servicedocumentatie", - "submit": "Voltooien ✓" + "title": "📊 Grafiekdata Export Sensor", + "description": "De Grafiekdata Export Sensor biedt prijsgegevens als sensor attributen.\n\n⚠️ **Let op:** Deze sensor is een legacy functie voor compatibiliteit met oudere tools.\n\n**Aanbevolen voor nieuwe setups:** Gebruik de `tibber_prices.get_chartdata` **service direct** - het is flexibeler, efficïnter, en de moderne Home Assistant aanpak.\n\n**Wanneer deze sensor zinvol is:**\n\n✅ Je dashboardtool kan **alleen** attributen lezen (geen service calls)\n✅ Je hebt statische data nodig die automatisch update\n❌ **Niet voor automatiseringen:** Gebruik `tibber_prices.get_chartdata` daar direct - flexibeler en efficïnter!\n\n---\n\n**De sensor inschakelen:**\n\n1. Open **Instellingen → Apparaten & Services → Tibber Prices**\n2. Selecteer je huis → Vind **'Chart Data Export'** (Diagnose sectie)\n3. **Schakel de sensor in** (standaard uitgeschakeld)\n\n**Configuratie (optioneel):**\n\nStandaard instellingen werken out-of-the-box (vandaag+morgen, 15-minuten intervallen, alleen prijzen).\n\nVoor aanpassing, voeg toe aan **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alle parameters:** Zie `tibber_prices.get_chartdata` service documentatie", + "submit": "↩ Ok & Terug" + }, + "reset_to_defaults": { + "title": "🔄 Standaardwaarden Herstellen", + "description": "⚠️ **Waarschuwing:** Dit zal **ALLE** instellingen naar fabrieksinstellingen terugzetten.\n\n**Wat wordt gereset:**\n• Alle prijsbeoordeling drempelwaarden\n• Alle volatiliteit drempelwaarden\n• Alle prijstrend drempelwaarden\n• Alle beste prijsperiode instellingen\n• Alle piekprijs periode instellingen\n• Weergave-instellingen\n• Algemene instellingen\n\n**Wat wordt NIET gereset:**\n• Je Tibber API-token\n• Geselecteerd huis\n• Valuta\n\n**💡 Tip:** Dit is handig als je opnieuw wilt beginnen na het experimenteren met instellingen.", + "data": { + "confirm_reset": "Ja, reset alles naar standaardwaarden" + }, + "submit": "Nu Resetten" } }, "error": { "auth": "Het Tibber-toegangstoken is ongeldig.", "connection": "Kan geen verbinding maken met Tibber. Controleer je internetverbinding.", - "unknown": "Er is een onverwachte fout opgetreden. Controleer de logboeken voor details.", + "unknown": "Er is een onverwachte fout opgetreden. Controleer de logs voor details.", "cannot_connect": "Verbinding mislukt", "invalid_access_token": "Ongeldig toegangstoken", - "different_home": "Het toegangstoken is niet geldig voor de huis-ID waarvoor deze integratie is geconfigureerd.", - "invalid_flex": "Flexibiliteitspercentage moet tussen -50% en +50% liggen", - "invalid_best_price_distance": "Afstandspercentage moet tussen -50% en 0% liggen (negatief = onder gemiddelde)", - "invalid_peak_price_distance": "Afstandspercentage moet tussen 0% en 50% liggen (positief = boven gemiddelde)", - "invalid_min_periods": "Minimum aantal perioden moet tussen 1 en 10 liggen", - "invalid_period_length": "De periodelengte moet minimaal 15 minuten zijn (veelvouden van 15).", - "invalid_gap_count": "Gaptolerantie moet tussen 0 en 8 liggen", - "invalid_relaxation_attempts": "Versoepelingspogingen moeten tussen 1 en 12 liggen", - "invalid_price_rating_low": "Lage prijsbeoordelingsdrempel moet tussen -50% en -5% liggen", - "invalid_price_rating_high": "Hoge prijsbeoordelingsdrempel moet tussen 5% en 50% liggen", + "different_home": "Het toegangstoken is niet geldig voor het huis-ID waarvoor deze integratie is geconfigureerd.", + "invalid_period_length": "Periode lengte moet minimaal 15 minuten zijn (veelvouden van 15).", + "invalid_flex": "Flexibiliteitspercentage moet tussen -50% en +50% zijn", + "invalid_best_price_distance": "Afstandspercentage moet tussen -50% en 0% zijn (negatief = onder gemiddelde)", + "invalid_peak_price_distance": "Afstandspercentage moet tussen 0% en 50% zijn (positief = boven gemiddelde)", + "invalid_min_periods": "Minimaal aantal periodes moet tussen 1 en 10 zijn", + "invalid_gap_count": "Gat telling moet tussen 0 en 8 zijn", + "invalid_relaxation_attempts": "Relaxatie pogingen moeten tussen 1 en 12 zijn", + "invalid_price_rating_low": "Lage prijsbeoordeling drempel moet tussen -50% en -5% zijn", + "invalid_price_rating_high": "Hoge prijsbeoordeling drempel moet tussen 5% en 50% zijn", "invalid_price_rating_thresholds": "Lage drempel moet lager zijn dan hoge drempel", - "invalid_volatility_threshold_moderate": "Gematigde volatiliteitsdrempel moet tussen 5% en 25% liggen", - "invalid_volatility_threshold_high": "Hoge volatiliteitsdrempel moet tussen 20% en 40% liggen", - "invalid_volatility_threshold_very_high": "Zeer hoge volatiliteitsdrempel moet tussen 35% en 80% liggen", - "invalid_volatility_thresholds": "Drempels moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog", - "invalid_price_trend_rising": "Stijgende trenddrempel moet tussen 1% en 50% liggen", - "invalid_price_trend_falling": "Dalende trenddrempel moet tussen -50% en -1% liggen" + "invalid_volatility_threshold_moderate": "Gematigde volatiliteit drempel moet tussen 5% en 25% zijn", + "invalid_volatility_threshold_high": "Hoge volatiliteit drempel moet tussen 20% en 40% zijn", + "invalid_volatility_threshold_very_high": "Zeer hoge volatiliteit drempel moet tussen 35% en 80% zijn", + "invalid_volatility_thresholds": "Drempelwaarden moeten in oplopende volgorde zijn: gematigd < hoog < zeer hoog", + "invalid_price_trend_rising": "Stijgende trend drempel moet tussen 1% en 50% zijn", + "invalid_price_trend_falling": "Dalende trend drempel moet tussen -50% en -1% zijn" }, "abort": { - "entry_not_found": "Tibber-configuratie-item niet gevonden." + "entry_not_found": "Tibber-configuratie-item niet gevonden.", + "reset_cancelled": "Reset geannuleerd. Er zijn geen wijzigingen aangebracht in je configuratie.", + "reset_successful": "✅ Alle instellingen zijn teruggezet naar fabrieksinstellingen. Je configuratie is nu als een nieuwe installatie.", + "finished": "Configuratie voltooid." } }, "entity": { "sensor": { "current_interval_price": { - "name": "Huidige elektriciteitsprijs" + "name": "Huidige Elektriciteitsprijs" }, "current_interval_price_base": { - "name": "Huidige elektriciteitsprijs (Energie-dashboard)" + "name": "Huidige Elektriciteitsprijs (Energie Dashboard)" }, "next_interval_price": { - "name": "Volgende elektriciteitsprijs" + "name": "Volgende Elektriciteitsprijs" }, "previous_interval_price": { - "name": "Vorige elektriciteitsprijs" + "name": "Vorige Elektriciteitsprijs" }, "current_hour_average_price": { - "name": "⌀ Uurprijs huidig" + "name": "⌀ Uurprijs Huidig" }, "next_hour_average_price": { - "name": "⌀ Uurprijs volgend" + "name": "⌀ Uurprijs Volgend" }, "current_interval_price_level": { - "name": "Huidig prijsniveau", + "name": "Huidig Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "next_interval_price_level": { - "name": "Volgend prijsniveau", + "name": "Volgend Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "previous_interval_price_level": { - "name": "Vorig prijsniveau", + "name": "Vorig Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "current_hour_price_level": { - "name": "Huidig uurprijsniveau", + "name": "Huidig Uur Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "next_hour_price_level": { - "name": "Volgend uurprijsniveau", + "name": "Volgend Uur Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "lowest_price_today": { - "name": "Laagste prijs vandaag" + "name": "Laagste Prijs Vandaag" }, "highest_price_today": { - "name": "Hoogste prijs vandaag" + "name": "Hoogste Prijs Vandaag" }, "average_price_today": { - "name": "⌀ Prijs vandaag" + "name": "⌀ Prijs Vandaag" }, "lowest_price_tomorrow": { - "name": "Laagste prijs morgen" + "name": "Laagste Prijs Morgen" }, "highest_price_tomorrow": { - "name": "Hoogste prijs morgen" + "name": "Hoogste Prijs Morgen" }, "average_price_tomorrow": { - "name": "⌀ Prijs morgen" + "name": "⌀ Prijs Morgen" }, "yesterday_price_level": { - "name": "Prijsniveau gisteren", + "name": "Gisteren Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "today_price_level": { - "name": "Prijsniveau vandaag", + "name": "Vandaag Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "tomorrow_price_level": { - "name": "Prijsniveau morgen", + "name": "Morgen Prijsniveau", "state": { - "very_cheap": "Zeer goedkoop", + "very_cheap": "Zeer Goedkoop", "cheap": "Goedkoop", "normal": "Normaal", "expensive": "Duur", - "very_expensive": "Zeer duur" + "very_expensive": "Zeer Duur" } }, "yesterday_price_rating": { - "name": "Prijsbeoordeling gisteren", + "name": "Gisteren Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -469,7 +476,7 @@ } }, "today_price_rating": { - "name": "Prijsbeoordeling vandaag", + "name": "Vandaag Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -477,7 +484,7 @@ } }, "tomorrow_price_rating": { - "name": "Prijsbeoordeling morgen", + "name": "Morgen Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -485,25 +492,25 @@ } }, "trailing_price_average": { - "name": "⌀ Prijs voortschrijdend 24u" + "name": "⌀ Prijs Afgelopen 24u" }, "leading_price_average": { - "name": "⌀ Prijs vooruitlopend 24u" + "name": "⌀ Prijs Komende 24u" }, "trailing_price_min": { - "name": "24u-minimumprijs voortschrijdend" + "name": "Afgelopen 24u Minimumprijs" }, "trailing_price_max": { - "name": "24u-maximumprijs voortschrijdend" + "name": "Afgelopen 24u Maximumprijs" }, "leading_price_min": { - "name": "24u-minimumprijs vooruitlopend" + "name": "Komende 24u Minimumprijs" }, "leading_price_max": { - "name": "24u-maximumprijs vooruitlopend" + "name": "Komende 24u Maximumprijs" }, "current_interval_price_rating": { - "name": "Huidige prijsbeoordeling", + "name": "Huidige Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -511,7 +518,7 @@ } }, "next_interval_price_rating": { - "name": "Volgende prijsbeoordeling", + "name": "Volgende Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -519,7 +526,7 @@ } }, "previous_interval_price_rating": { - "name": "Vorige prijsbeoordeling", + "name": "Vorige Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -527,7 +534,7 @@ } }, "current_hour_price_rating": { - "name": "Huidig uurprijsbeoordeling", + "name": "Huidig Uur Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -535,7 +542,7 @@ } }, "next_hour_price_rating": { - "name": "Volgend uurprijsbeoordeling", + "name": "Volgend Uur Prijsbeoordeling", "state": { "low": "Laag", "normal": "Normaal", @@ -543,28 +550,28 @@ } }, "next_avg_1h": { - "name": "⌀ Prijs volgende 1u" + "name": "⌀ Prijs Komende 1u" }, "next_avg_2h": { - "name": "⌀ Prijs volgende 2u" + "name": "⌀ Prijs Komende 2u" }, "next_avg_3h": { - "name": "⌀ Prijs volgende 3u" + "name": "⌀ Prijs Komende 3u" }, "next_avg_4h": { - "name": "⌀ Prijs volgende 4u" + "name": "⌀ Prijs Komende 4u" }, "next_avg_5h": { - "name": "⌀ Prijs volgende 5u" + "name": "⌀ Prijs Komende 5u" }, "next_avg_6h": { - "name": "⌀ Prijs volgende 6u" + "name": "⌀ Prijs Komende 6u" }, "next_avg_8h": { - "name": "⌀ Prijs volgende 8u" + "name": "⌀ Prijs Komende 8u" }, "next_avg_12h": { - "name": "⌀ Prijs volgende 12u" + "name": "⌀ Prijs Komende 12u" }, "price_trend_1h": { "name": "Prijstrend (1u)", @@ -631,7 +638,7 @@ } }, "current_price_trend": { - "name": "Huidige prijstrend", + "name": "Huidige Prijstrend", "state": { "rising": "Stijgend", "falling": "Dalend", @@ -639,16 +646,27 @@ } }, "next_price_trend_change": { - "name": "Volgende trendwijziging" + "name": "Volgende Prijstrend Wijziging" }, "daily_rating": { - "name": "Dagelijkse prijsbeoordeling" + "name": "Dagelijkse Prijsbeoordeling" }, "monthly_rating": { - "name": "Maandelijkse prijsbeoordeling" + "name": "Maandelijkse Prijsbeoordeling" + }, + "data_lifecycle_status": { + "name": "Data Levenscyclus Status", + "state": { + "cached": "Gecachet", + "fresh": "Vers", + "refreshing": "Vernieuwen", + "searching_tomorrow": "Morgen Zoeken", + "turnover_pending": "Omslag In Behandeling", + "error": "Fout" + } }, "today_volatility": { - "name": "Volatiliteit vandaag", + "name": "Vandaag Prijsvolatiliteit", "state": { "low": "Laag", "moderate": "Gematigd", @@ -657,7 +675,7 @@ } }, "tomorrow_volatility": { - "name": "Volatiliteit morgen", + "name": "Morgen Prijsvolatiliteit", "state": { "low": "Laag", "moderate": "Gematigd", @@ -666,7 +684,7 @@ } }, "next_24h_volatility": { - "name": "Volatiliteit volgende 24u", + "name": "Komende 24u Prijsvolatiliteit", "state": { "low": "Laag", "moderate": "Gematigd", @@ -675,7 +693,7 @@ } }, "today_tomorrow_volatility": { - "name": "Volatiliteit vandaag+morgen", + "name": "Vandaag+Morgen Prijsvolatiliteit", "state": { "low": "Laag", "moderate": "Gematigd", @@ -684,186 +702,175 @@ } }, "best_price_end_time": { - "name": "Beste prijs eindigt" + "name": "Beste Prijs Einde" }, "best_price_period_duration": { - "name": "Beste prijs duur" + "name": "Beste Prijs Duur" }, "best_price_remaining_minutes": { - "name": "Beste prijs resterende tijd" + "name": "Beste Prijs Resterende Tijd" }, "best_price_progress": { - "name": "Beste prijs voortgang" + "name": "Beste Prijs Voortgang" }, "best_price_next_start_time": { - "name": "Beste prijs start" + "name": "Beste Prijs Start" }, "best_price_next_in_minutes": { - "name": "Beste prijs start over" + "name": "Beste Prijs Start Over" }, "peak_price_end_time": { - "name": "Piekprijs eindigt" + "name": "Piekprijs Einde" }, "peak_price_period_duration": { - "name": "Piekprijs duur" + "name": "Piekprijs Duur" }, "peak_price_remaining_minutes": { - "name": "Piekprijs resterende tijd" + "name": "Piekprijs Resterende Tijd" }, "peak_price_progress": { - "name": "Piekprijs voortgang" + "name": "Piekprijs Voortgang" }, "peak_price_next_start_time": { - "name": "Piekprijs start" + "name": "Piekprijs Start" }, "peak_price_next_in_minutes": { - "name": "Piekprijs start over" + "name": "Piekprijs Start Over" }, "home_type": { - "name": "Woningtype", + "name": "Huistype", "state": { "apartment": "Appartement", "rowhouse": "Rijtjeshuis", "house": "Huis", - "cottage": "Vakantiehuis" + "cottage": "Huisje" } }, "home_size": { - "name": "Woonoppervlakte" + "name": "Huisgrootte" }, "main_fuse_size": { - "name": "Hoofdzekering" + "name": "Hoofdzekering Grootte" }, "number_of_residents": { - "name": "Aantal bewoners" + "name": "Aantal Bewoners" }, "primary_heating_source": { - "name": "Primaire warmtebron", + "name": "Primaire Verwarmingsbron", "state": { - "air2air_heatpump": "Lucht-lucht-warmtepomp", - "air2water_heatpump": "Lucht-water-warmtepomp", + "air2air_heatpump": "Lucht-naar-Lucht Warmtepomp", + "air2water_heatpump": "Lucht-naar-Water Warmtepomp", "boiler": "Boiler", - "central_heating": "Centrale verwarming", + "central_heating": "Centrale Verwarming", "district_heating": "Stadsverwarming", "district": "Stadsverwarming", - "electric_boiler": "Elektrische boiler", + "electric_boiler": "Elektrische Boiler", "electricity": "Elektriciteit", "floor": "Vloerverwarming", "gas": "Gas", - "ground_heatpump": "Bodemwarmtepomp", - "ground": "Bodemwarmtepomp", + "ground_heatpump": "Bodem Warmtepomp", + "ground": "Bodem Warmtepomp", "oil": "Olie", - "other": "Anders", + "other": "Overig", "waste": "Restwarmte" } }, "grid_company": { - "name": "Netbeheerder" + "name": "Netbedrijf" }, "grid_area_code": { "name": "Netgebiedcode" }, "price_area_code": { - "name": "Prijszonecode" + "name": "Prijsgebiedcode" }, "consumption_ean": { - "name": "Verbruiks-EAN" + "name": "Verbruik EAN" }, "production_ean": { - "name": "Productie-EAN" + "name": "Productie EAN" }, "energy_tax_type": { - "name": "Energiebelastingtype" + "name": "Energiebelasting Type" }, "vat_type": { - "name": "BTW-type" + "name": "BTW Type" }, "estimated_annual_consumption": { - "name": "Geschat jaarverbruik" + "name": "Geschat Jaarverbruik" }, "subscription_status": { - "name": "Abonnementsstatus", + "name": "Abonnement Status", "state": { "running": "Actief", "ended": "Beëindigd", - "pending": "In afwachting", + "pending": "In Afwachting", "unknown": "Onbekend" } }, "chart_data_export": { - "name": "Grafiek Data Export", + "name": "Grafiekdata Export", "state": { - "pending": "In afwachting", - "ready": "Klaar", + "pending": "In Afwachting", + "ready": "Gereed", "error": "Fout" } }, "chart_metadata": { - "name": "Grafiekmetadata", + "name": "Grafiek Metadata", "state": { - "pending": "In afwachting", - "ready": "Klaar", - "error": "Fout" - } - }, - "data_lifecycle_status": { - "name": "Datalevenscyclus-status", - "state": { - "cached": "In cache", - "fresh": "Vers", - "refreshing": "Vernieuwen", - "searching_tomorrow": "Zoekt morgengegevens", - "turnover_pending": "Middernachtwissel in behandeling", + "pending": "In Afwachting", + "ready": "Gereed", "error": "Fout" } } }, "binary_sensor": { "peak_price_period": { - "name": "Piekprijs-periode" + "name": "Piekprijs Periode" }, "best_price_period": { - "name": "Beste prijs-periode" + "name": "Beste Prijs Periode" }, "connection": { - "name": "Tibber API-verbinding" + "name": "Tibber API Verbinding" }, "tomorrow_data_available": { - "name": "Gegevens van morgen beschikbaar" + "name": "Morgen Gegevens Beschikbaar" }, "has_ventilation_system": { - "name": "Heeft ventilatiesysteem" + "name": "Heeft Ventilatiesysteem" }, "realtime_consumption_enabled": { - "name": "Realtime verbruik ingeschakeld" + "name": "Realtime Verbruik Ingeschakeld" } } }, "issues": { "new_homes_available": { - "title": "Nieuwe Tibber-huizen gedetecteerd", - "description": "We hebben {count} nieuw(e) huis/huizen op je Tibber-account gedetecteerd: {homes}. Je kunt ze toevoegen aan Home Assistant via de Tibber-integratieconfiguratie." + "title": "Nieuwe Tibber huizen gedetecteerd", + "description": "We hebben {count} nieuw(e) huis/huizen gedetecteerd op je Tibber-account: {homes}. Je kunt ze toevoegen aan Home Assistant via de Tibber integratie configuratie." }, "homes_removed": { - "title": "Tibber-huizen verwijderd", - "description": "We hebben gedetecteerd dat {count} huis/huizen zijn verwijderd van je Tibber-account: {homes}. Controleer je Tibber-integratieconfiguratie." + "title": "Tibber huizen verwijderd", + "description": "We hebben gedetecteerd dat {count} huis/huizen zijn verwijderd van je Tibber-account: {homes}. Controleer je Tibber integratie configuratie." }, "tomorrow_data_missing": { - "title": "Prijsgegevens voor morgen ontbreken voor {home_name}", - "description": "De elektriciteitsprijsgegevens voor morgen zijn nog steeds niet beschikbaar na {warning_hour}:00 uur. Dit is ongebruikelijk, aangezien Tibber doorgaans de prijzen voor morgen in de middag publiceert (rond 13:00-14:00 CET).\n\nMogelijke oorzaken:\n- Tibber heeft de prijzen voor morgen nog niet gepubliceerd\n- Tijdelijke API-problemen\n- Je elektriciteitsleverancier heeft nog geen prijzen aan Tibber ingediend\n\nDit probleem wordt automatisch opgelost zodra de gegevens voor morgen beschikbaar zijn. Als dit na 20:00 uur aanhoudt, controleer dan de Tibber-app of neem contact op met Tibber-ondersteuning." + "title": "Morgen prijsgegevens ontbreken voor {home_name}", + "description": "Morgen elektriciteitsprijs gegevens zijn nog steeds niet beschikbaar na {warning_hour}:00. Dit is ongebruikelijk, omdat Tibber normaal gesproken morgen prijzen publiceert in de middag (rond 13:00-14:00 CET).\n\nMogelijke oorzaken:\n- Tibber heeft morgen prijzen nog niet gepubliceerd\n- Tijdelijke API-problemen\n- Je elektriciteitsleverancier heeft nog geen prijzen ingediend bij Tibber\n\nDit probleem lost zich automatisch op zodra morgen gegevens beschikbaar worden. Als dit na 20:00 aanhoudt, controleer dan de Tibber-app of neem contact op met Tibber support." }, "rate_limit_exceeded": { - "title": "API-snelheidslimiet overschreden voor {home_name}", - "description": "De Tibber-API heeft deze integratie beperkt na {error_count} opeenvolgende fouten. Dit betekent dat verzoeken te vaak worden gedaan.\n\nDe integratie zal automatisch opnieuw proberen met toenemende vertragingen. Dit probleem wordt opgelost zodra de snelheidslimiet verloopt.\n\nAls dit meerdere uren aanhoudt, overweeg dan:\n- Controleren of meerdere Home Assistant-instanties hetzelfde API-token gebruiken\n- Verifiëren dat geen andere applicaties je Tibber-API-token intensief gebruiken\n- De updatefrequentie verminderen als je deze hebt aangepast" + "title": "API rate limiet overschreden voor {home_name}", + "description": "De Tibber API heeft deze integratie beperkt na {error_count} opeenvolgende fouten. Dit betekent dat verzoeken te frequent worden gedaan.\n\nDe integratie zal automatisch opnieuw proberen met toenemende vertragingen. Dit probleem lost zich op zodra de rate limiet verloopt.\n\nAls dit meerdere uren aanhoudt, overweeg dan:\n- Te controleren of meerdere Home Assistant instanties hetzelfde API-token gebruiken\n- Te verifiëren dat geen andere applicaties intensief je Tibber API-token gebruiken\n- De update frequentie te verlagen als je deze hebt aangepast" }, "home_not_found": { "title": "Huis {home_name} niet gevonden in Tibber-account", - "description": "Het huis geconfigureerd in deze integratie (entry ID: {entry_id}) is niet langer beschikbaar in je Tibber-account. Dit gebeurt meestal wanneer:\n- Het huis is verwijderd uit je Tibber-account\n- Het huis is verplaatst naar een ander Tibber-account\n- Toegang tot dit huis is ingetrokken\n\nVerwijder deze integratie-entry en voeg deze opnieuw toe als het huis nog steeds moet worden gemonitord. Om deze entry te verwijderen, ga naar Instellingen → Apparaten & Diensten → Tibber Prices en verwijder de {home_name}-configuratie." + "description": "Het huis geconfigureerd in deze integratie (entry ID: {entry_id}) is niet langer beschikbaar in je Tibber-account. Dit gebeurt meestal wanneer:\n- Het huis is verwijderd uit je Tibber-account\n- Het huis is verplaatst naar een ander Tibber-account\n- Toegang tot dit huis is ingetrokken\n\nVerwijder dit integratie-item en voeg het opnieuw toe als het huis nog steeds gemonitord moet worden. Om dit item te verwijderen, ga naar Instellingen → Apparaten & Services → Tibber Prices en verwijder de {home_name} configuratie." } }, "services": { "get_price": { - "name": "Prijsgegevens ophalen", + "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": { @@ -903,7 +910,7 @@ } }, "get_chartdata": { - "name": "Haal grafiekgegevens op", + "name": "Grafiekdata Ophalen", "description": "Geeft prijsgegevens terug in een eenvoudig grafiekvriendelijk formaat compatibel met de Tibber Core-integratie outputstructuur. Perfect voor gebruik met populaire grafiekkaarten zoals ha-price-timeline-card, ApexCharts Card, Plotly Graph Card, Mini Graph Card of de ingebouwde History Graph Card. Veldnamen en gegevensstructuur kunnen worden aangepast aan de vereisten van je grafiek.", "sections": { "general": { @@ -1048,7 +1055,7 @@ "selector": { "account_choice": { "options": { - "new_token": "Nieuw Tibber-account toevoegen met API-token" + "new_token": "Nieuw Tibber account API-token toevoegen" } }, "day": { @@ -1056,26 +1063,26 @@ "yesterday": "Gisteren", "today": "Vandaag", "tomorrow": "Morgen", - "rolling_window": "Rollend venster", - "rolling_window_autozoom": "Rollend venster (Auto-Zoom)" + "rolling_window": "Schuivend Venster", + "rolling_window_autozoom": "Schuivend Venster (Auto-Zoom)" } }, "resolution": { "options": { "interval": "Interval (15 min)", - "hourly": "Per uur" + "hourly": "Per Uur" } }, "output_format": { "options": { - "array_of_objects": "Array van objecten", - "array_of_arrays": "Array van arrays" + "array_of_objects": "Array van Objecten", + "array_of_arrays": "Array van Arrays" } }, "level_type": { "options": { "rating_level": "Beoordelingsniveau (laag/normaal/hoog)", - "level": "Tibber-niveau (zeer goedkoop tot zeer duur)" + "level": "Tibber Niveau (zeer goedkoop tot zeer duur)" } }, "level_filter": { @@ -1098,18 +1105,18 @@ "options": { "none": "Geen", "segments": "Segmenten", - "all": "Alle" + "all": "Alles" } }, "period_filter": { "options": { - "best_price": "Beste prijsperioden", - "peak_price": "Piek prijsperioden" + "best_price": "Beste Prijs Periodes", + "peak_price": "Piekprijs Periodes" } }, "metadata": { "options": { - "include": "Opnemen (data + metadata)", + "include": "Inbegrepen (data + metadata)", "only": "Alleen metadata", "none": "Geen (alleen data)" } @@ -1117,14 +1124,14 @@ "volatility": { "options": { "low": "Laag", - "moderate": "Matig", + "moderate": "Gematigd", "high": "Hoog", "very_high": "Zeer hoog" } }, "current_interval_price_level": { "options": { - "any": "Alle", + "any": "Alles", "very_cheap": "Zeer goedkoop", "cheap": "Goedkoop", "normal": "Normaal", @@ -1135,13 +1142,13 @@ "currency_display_mode": { "options": { "base": "Basisvaluta (€, kr)", - "subunit": "Subeenheid valuta (ct, øre)" + "subunit": "Subeenheid Valuta (ct, øre)" } }, "average_sensor_display": { "options": { "median": "Mediaan", - "mean": "Rekenkundig gemiddelde" + "mean": "Rekenkundig Gemiddelde" } } }, diff --git a/custom_components/tibber_prices/translations/sv.json b/custom_components/tibber_prices/translations/sv.json index 627c211..986e55e 100644 --- a/custom_components/tibber_prices/translations/sv.json +++ b/custom_components/tibber_prices/translations/sv.json @@ -3,7 +3,7 @@ "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.", + "description": "Du kan lägga till ett annat hem från ett befintligt Tibber-konto eller ange en ny API-token för ett annat konto.", "data": { "account_choice": "Konto" }, @@ -41,8 +41,8 @@ "submit": "Välj hem" }, "reauth_confirm": { - "title": "Autentisera Tibber Prisintegrationen igen", - "description": "Åtkomsttoken för Tibber är inte längre giltig. Vänligen ange en ny API-åtkomsttoken för att fortsätta använda denna integration.\n\nFör att generera en ny API-åtkomsttoken, besök https://developer.tibber.com.", + "title": "Återautentisera Tibber-prisintegration", + "description": "Åtkomsttoken för Tibber är inte längre giltig. Ange en ny API-åtkomsttoken för att fortsätta använda denna integration.\n\nFör att generera en ny API-åtkomsttoken, besök https://developer.tibber.com.", "data": { "access_token": "API-åtkomsttoken" }, @@ -51,29 +51,29 @@ }, "error": { "auth": "Tibber-åtkomsttoken är ogiltig.", - "connection": "Kunde inte ansluta till Tibber. Vänligen kontrollera din internetanslutning.", + "connection": "Kan inte ansluta till Tibber. Kontrollera din internetanslutning.", "unknown": "Oväntat fel", - "cannot_connect": "Kunde inte ansluta", + "cannot_connect": "Anslutning misslyckades", "invalid_access_token": "Ogiltig åtkomsttoken", "missing_homes": "Den nya åtkomsttoken har inte åtkomst till alla konfigurerade hem. Använd en åtkomsttoken som har åtkomst till samma Tibber-hem.", - "home_already_configured": "Detta hem är redan konfigurerat i en annan post. Varje hem kan endast konfigureras en gång.", + "home_already_configured": "Detta hem är redan konfigurerat i en annan post. Varje hem kan bara 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.", + "subscription_expired": "Tibber-avtalet för detta hem har löpt ut. Endast hem med aktiva eller framtida elavtal kan läggas till i Home Assistant.", + "future_subscription_warning": "Obs: Detta hems Tibber-avtal har inte börjat ännu. 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.", - "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.", + "invalid_yaml_structure": "YAML måste vara en ordbok/objekt (nyckel: värde-par), inte en lista eller vanlig text.", + "service_call_failed": "Tjänsteanrop validering misslyckades: {error_detail}", + "missing_entry_id": "Post-ID krävs men angavs inte.", + "invalid_entry_id": "Ogiltigt 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." + "price_fetch_failed": "Misslyckades med att hämta prisdata. Kontrollera loggarna för detaljer." }, "abort": { - "already_configured": "Alla tillgängliga Tibber-hem är redan konfigurerade. Varje hem kan endast konfigureras en gång.", + "already_configured": "Alla tillgängliga Tibber-hem är redan konfigurerade. Varje hem kan bara konfigureras en gång.", "entry_not_found": "Tibber-konfigurationspost hittades inte.", - "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." + "setup_complete": "Konfiguration klar! Du kan ändra ytterligare alternativ för Tibber-priser i integrationens inställningar efter att ha stängt denna dialogruta.", + "reauth_successful": "Återautentisering lyckades. Integrationen har uppdaterats med den nya åtkomsttoken." } }, "common": { @@ -89,21 +89,21 @@ "step": { "user": { "title": "Välj konfigurationspost", - "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.", + "description": "Välj den konfigurationspost som du vill skapa en tidsresevy för.\n\n**Tidsresevyer** låter dig se historisk prisdata som om det vore nuvarande tid. Detta är användbart för att testa automationer eller analysera tidigare prismönster.", "data": { "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", + "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 \"igår\"-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." + "virtual_time_offset_days": "Hur många dagar ska man resa tillbaka i tiden. Skjutreglage: 0 till 374 dagar (≈1 år). Rekommenderat: ≥2 dagar för att undvika konflikter med \"igår\"-entiteter.", + "time_offset": "Valfri finjustering: Lägg till timmar och/eller minuter till dagförskjutningen. Tiden subtraheras automatiskt (resa tillbaka längre). Obs: Sekunder ignoreras - endast minutnoggrannhet stöds." } }, "init": { @@ -114,17 +114,17 @@ "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." + "virtual_time_offset_days": "Hur många dagar ska man resa tillbaka i tiden. Skjutreglage: 0 till 374 dagar (≈1 år). Rekommenderat: ≥2 dagar för att undvika konflikter med \"igår\"-entiteter.", + "time_offset": "Valfri finjustering: Lägg till timmar och/eller minuter till dagförskjutningen. Tiden subtraheras automatiskt (resa tillbaka längre). Obs: Sekunder ignoreras - endast minutnoggrannhet stöds." } } }, "error": { - "no_time_offset": "Minst ett tidsförskjutningsvärde måste vara negativt (endast historiska data)." + "no_time_offset": "Minst ett tidsförskjutningsvärde måste vara negativt (endast historisk data)." }, "abort": { - "already_configured": "**En tidsresevy med denna exakta tidsförskjutning existerar redan.**\n\nVälj en annan förskjutning.", - "no_main_entries": "Inga huvudkonfigurationsposter hittades. Lägg först till ett Tibber-hem.", + "already_configured": "**En tidsresevy med exakt denna tidsförskjutning finns redan.**\n\nVälj en annan förskjutning.", + "no_main_entries": "Inga huvudkonfigurationsposter hittades. Lägg till ett Tibber-hem först.", "parent_entry_not_found": "Vald konfigurationspost hittades inte." } } @@ -132,202 +132,208 @@ "options": { "step": { "init": { + "menu_options": { + "general_settings": "⚙️ Allmänna inställningar", + "display_settings": "💱 Valutavisning", + "current_interval_price_rating": "📊 Prisbetyg", + "volatility": "💨 Prisvolatilitet", + "best_price": "💚 Bästa Prisperiod", + "peak_price": "🔴 Topprisperiod", + "price_trend": "📈 Pristrend", + "chart_data_export": "📊 Diagramdataexport-sensor", + "reset_to_defaults": "🔄 Återställ till standard", + "finish": "⬅️ Tillbaka" + } + }, + "general_settings": { "title": "⚙️ Allmänna inställningar", - "description": "_{step_progress}_\n\n**Konfigurera allmänna inställningar för Tibber-prisinformation och betyg.**\n\n---\n\n**Användare:** {user_login}", + "description": "**Konfigurera allmänna inställningar för Tibber prisinformation och betyg.**\n\n---\n\n**Användare:** {user_login}", "data": { - "extended_descriptions": "Utökade beskrivningar" + "extended_descriptions": "Utökade beskrivningar", + "average_sensor_display": "Visa genomsnittssensor" }, "data_description": { - "extended_descriptions": "Styr om entitetsattribut inkluderar detaljerade förklaringar och användningstips.\n\n• Inaktiverad (standard): Bara kort beskrivning\n• Aktiverad: Detaljerad förklaring + praktiska användningsexempel\n\nExempel:\nInaktiverad = 1 attribut\nAktiverad = 2 extra attribut" + "extended_descriptions": "Kontrollerar om entitetsattribut inkluderar detaljerade förklaringar och användningstips.\n\n• Inaktiverad (standard): Endast kort beskrivning\n• Aktiverad: Detaljerad förklaring + praktiska användningsexempel\n\nExempel:\nInaktiverad = 1 attribut\nAktiverad = 2 ytterligare attribut", + "average_sensor_display": "Välj vilket statistiskt mått som ska visas i sensortillståndet för genomsnittsprissensorer. Det andra värdet visas som ett attribut. Median är mer resistent mot extremvärden, medan aritmetiskt medelvärde representerar det traditionella genomsnittet. Standard: Median" }, - "submit": "Fortsätt →" + "submit": "↩ Spara & tillbaka" }, "display_settings": { "title": "💱 Valutavisningsinställningar", - "description": "_{step_progress}_\n\n**Konfigurera hur elpriser visas - i basvaluta (€, kr) eller underenhet (ct, öre).**\n\n---", + "description": "**Konfigurera hur elpriser visas - i basvaluta (€, kr) eller underenhet (ct, øre).**\n\n---", "data": { "currency_display_mode": "Visningsläge" }, "data_description": { - "currency_display_mode": "Välj hur priser visas:\n\n• **Basvaluta** (€/kWh, kr/kWh): Decimalvärden (t.ex. 0,25 €/kWh) - skillnader synliga från 3:e-4:e decimalen\n• **Underenhet** (ct/kWh, öre/kWh): Större värden (t.ex. 25,00 ct/kWh) - skillnader redan synliga från 1:a decimalen\n\nStandard beror på din valuta:\n• EUR → Underenhet (cent) - tysk/nederländsk preferens\n• NOK/SEK/DKK → Basvaluta (kronor) - skandinavisk preferens\n• USD/GBP → Basvaluta\n\n**💡 Tips:** Vid val av underenhet kan du aktivera den extra sensorn \"Nuvarande elpris (Energipanel)\" (inaktiverad som standard)." + "currency_display_mode": "Välj hur priser visas:\n\n• **Basvaluta** (€/kWh, kr/kWh): Decimalvärden (t.ex. 0,25 €/kWh) - skillnader synliga från 3:e-4:e decimalen\n• **Underenhetsvaluta** (ct/kWh, øre/kWh): Större värden (t.ex. 25,00 ct/kWh) - skillnader synliga från 1:a decimalen\n\nStandard beror på din valuta:\n• EUR → Underenhet (cent) - Tysk/nederländsk preferens\n• NOK/SEK/DKK → Bas (kronor) - Skandinavisk preferens\n• USD/GBP → Basvaluta\n\n**💡 Tips:** När du väljer Underenhetsvaluta kan du aktivera den extra sensorn \"Aktuellt Elpris (Energidashboard)\" (inaktiverad som standard)." }, - "submit": "Fortsätt →" + "submit": "↩ Spara & tillbaka" }, "current_interval_price_rating": { - "title": "📊 Prisvärderingströsklar", - "description": "_{step_progress}_\n\n**Konfigurera trösklar för prisvärderingsnivåer (låg/normal/hög) baserat på jämförelse med rullande 24-timmars genomsnitt.**\n\n---", - "sections": { - "price_rating_thresholds": { - "name": "Prisvärderingströsklar", - "description": "Definiera prisvärderingsnivåer.", - "data": { - "price_rating_threshold_low": "Låg tröskel", - "price_rating_threshold_high": "Hög tröskel" - }, - "data_description": { - "price_rating_threshold_low": "Procent under det rullande 24-timmars genomsnittet som det aktuella priset måste vara för att kvalificera som 'låg' värdering. Exempel: 5 betyder minst 5% under genomsnitt. Sensorer med denna värdering indikerar gynnsamma tidsfönster. Standard: 5%", - "price_rating_threshold_high": "Procent över det rullande 24-timmars genomsnittet som det aktuella priset måste vara för att kvalificera som 'hög' värdering. Exempel: 10 betyder minst 10% över genomsnitt. Sensorer med denna värdering varnar för dyra tidsfönster. Standard: 10%" - } - } + "title": "📊 Prisbetygströsklar", + "description": "**Konfigurera tröskelvärden för prisbetygsnivåer (låg/normal/hög) baserat på jämförelse med glidande 24-timmars genomsnitt.**", + "data": { + "price_rating_threshold_low": "Låg tröskel", + "price_rating_threshold_high": "Hög tröskel" }, - "submit": "Fortsätt →" + "data_description": { + "price_rating_threshold_low": "Procentandel under det glidande 24-timmars genomsnittet som det aktuella priset måste vara för att kvalificera som 'lågt' betyg. Exempel: 5 betyder minst 5% under genomsnittet. Sensorer med detta betyg indikerar gynnsamma tidsfönster. Standard: 5%", + "price_rating_threshold_high": "Procentandel över det glidande 24-timmars genomsnittet som det aktuella priset måste vara för att kvalificera som 'högt' betyg. Exempel: 10 betyder minst 10% över genomsnittet. Sensorer med detta betyg varnar om dyra tidsfönster. Standard: 10%" + }, + "submit": "↩ Spara & tillbaka" }, "best_price": { - "title": "💚 Bästa Pris Period Inställningar", - "description": "_{step_progress}_\n\nKonfigurera inställningar för **Bästa Pris Period** binär sensor. Denna sensor är aktiv under perioder med de lägsta elpriserna.\n\n---", + "title": "💚 Bästa Prisperiod-inställningar", + "description": "**Konfigurera inställningar för binärsensorn Bästa Prisperiod. Denna sensor är aktiv under perioder med lägsta elpriserna.**\n\n---", "sections": { "period_settings": { - "name": "Periodinställningar", - "description": "Konfigurera periodlängd och prisnivåbegränsningar.", + "name": "Periodlängd & Nivåer", + "description": "Konfigurera hur långa perioder ska vara och vilka prisnivåer som ska inkluderas.", "data": { - "best_price_min_period_length": "Minimal periodlängd", + "best_price_min_period_length": "Minsta Periodlängd", "best_price_max_level": "Prisnivåfilter", - "best_price_max_level_gap_count": "Gaptolerens" + "best_price_max_level_gap_count": "Gaptolerans" }, "data_description": { - "best_price_min_period_length": "Minsta varaktighet för att en period ska räknas som 'bästa pris'. Längre perioder är mer praktiska för att köra hushållsmaskiner som diskmaskiner eller värmepumpar. Bästa pris-perioder kräver minst 60 minuter (jämfört med 30 minuter för topppris-varningar) eftersom de ska ge meningsfulla tidsfönster för förbrukningsplanering, inte bara kortvariga möjligheter.", - "best_price_max_level": "Visa endast bästa pris-perioder om de innehåller intervall med prisnivåer ≤ valt värde. Till exempel måste perioden om du väljer '**Billigt**' ha minst ett '**Mycket billigt**' eller '**Billigt**' intervall. Detta säkerställer att 'bästa pris'-perioder inte bara är relativt billiga för dagen, utan faktiskt billiga i absoluta termer. Välj '**Alla**' för att visa bästa priser oavsett deras absoluta prisnivå.", - "best_price_max_level_gap_count": "Maximalt antal på varandra följande intervaller som får avvika med exakt ett nivåsteg från det erforderliga nivået. Till exempel: med '**Billigt**' filter och gapantal 1 accepteras sekvensen '**Billigt**, **Billigt**, **Normal**, **Billigt**' (**Normal** är ett steg över **Billigt**). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. **Obs:** Gaptoleransen kräver perioder ≥90 minuter (6 intervaller) för att detektera avvikare effektivt. Standard: 0 (strikt filtrering, ingen tolerans)." + "best_price_min_period_length": "Minsta varaktighet för en period för att betraktas som 'bästa pris'. Längre perioder är mer praktiska för att köra apparater som diskmaskiner eller värmepumpar. Bästa prisperioder kräver 60 minuter minimum (jämfört med 30 minuter för topprisvarningar) eftersom de bör ge meningsfulla tidsfönster för förbrukningsplanering, inte bara korta möjligheter.", + "best_price_max_level": "Visa endast bästa prisperioder om de innehåller intervall med prisnivåer ≤ valt värde. Till exempel betyder valet '**Billigt**' att perioden måste ha minst ett '**Mycket billigt**' eller '**Billigt**' intervall. Detta säkerställer att 'bästa pris'-perioder inte bara är relativt billiga för dagen, utan faktiskt billiga i absoluta termer. Välj '**Alla**' för att visa bästa priser oavsett deras absoluta prisnivå.", + "best_price_max_level_gap_count": "Maximalt antal konsekutiva intervall som tillåts avvika med exakt ett nivåsteg från den nödvändiga nivån. Till exempel: med '**Billigt**'-filter och gapantal 1, accepteras sekvensen '**Billigt**, **Billigt**, **Normalt**, **Billigt**' (**Normalt** är ett steg över **Billigt**). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. **Obs:** Gaptolerans kräver perioder ≥90 minuter (6 intervall) för att upptäcka avvikare effektivt. Standard: 0 (strikt filtrering, ingen tolerans)." } }, "flexibility_settings": { - "name": "Flexibilitetsinställningar", - "description": "Konfigurera prisjämförelsegränser och filtrering.", + "name": "Flexibilitet & Trösklar", + "description": "Kontrollera hur mycket priser kan avvika och fortfarande kvalificera som 'bästa pris'.", "data": { "best_price_flex": "Flexibilitet", "best_price_min_distance_from_avg": "Minimiavstånd" }, "data_description": { - "best_price_flex": "Maximalt över dagens minimumpris där intervaller fortfarande kvalificerar som 'bästa pris'. Rekommenderat: 15-20 med avslappning aktiverad (standard), eller 25-35 utan avslappning. Maximum: 50 (hård gräns för tillförlitlig periodigkänning).", - "best_price_min_distance_from_avg": "Säkerställer att perioder är betydligt billigare än dagsgenomsnittet, inte bara marginellt under det. Detta filtrerar brus och förhindrar att något-under-genomsnittet perioder markeras som 'bästa pris' på dagar med platta priser. Högre värden = striktare filtrering (endast riktigt billiga perioder kvalificerar). Standard: 5 betyder att perioder måste vara minst 5% under dagsgenomsnittet." + "best_price_flex": "Maximum över dagens minimipris som intervall kan vara och fortfarande kvalificera som 'bästa pris'. Rekommenderat: 15-20 med relaxation aktiverad (standard), eller 25-35 utan relaxation. Maximum: 50 (hård gräns för pålitlig perioddetektering).", + "best_price_min_distance_from_avg": "Säkerställer att perioder är betydligt billigare än dagsgenomsnittet, inte bara marginellt under det. Detta filtrerar bort brus och förhindrar att perioder strax under genomsnittet markeras som 'bästa pris' på dagar med platta priser. Högre värden = striktare filtrering (endast verkligt billiga perioder kvalificerar). Standard: 5 betyder att perioder måste vara minst 5% under dagsgenomsnittet." } }, "relaxation_and_target_periods": { - "name": "Avslappning & Målperioder", - "description": "Konfigurera automatisk filteravslappning och målperiodantal. Aktivera 'Nå minimiantal' för att aktivera avslappning.", + "name": "Relaxation & Målperioder", + "description": "Konfigurera automatisk filterrelaxation och målperiodsantal. Aktivera 'Uppnå Minimiantal' för att aktivera relaxation.", "data": { - "enable_min_periods_best": "Nå minimiantal", - "min_periods_best": "Minimiperioder", - "relaxation_attempts_best": "Avslappningsförsök" + "enable_min_periods_best": "Uppnå Minimiantal", + "min_periods_best": "Minsta Perioder", + "relaxation_attempts_best": "Relaxationsförsök" }, "data_description": { - "enable_min_periods_best": "När den är aktiverad kommer filter gradvis att slappas av om inte tillräckligt många perioder hittas. Detta försöker nå det önskade minimiantal perioder, vilket kan inkludera mindre optimala tidsfönster som bästa pris-perioder.", - "min_periods_best": "Minimiantal bästa pris-perioder att sikta på per dag. Filter kommer att slappas av steg för steg för att försöka nå detta antal. Endast aktivt när 'Nå minimiantal' är aktiverad. Standard: 1", - "relaxation_attempts_best": "Hur många flexnivåer (försök) att prova innan man ger upp. Varje försök kör alla filterkombinationer på den nya flexnivån. Fler försök ökar chansen att hitta ytterligare perioder på bekostnad av längre bearbetningstid." + "enable_min_periods_best": "När aktiverad kommer filter gradvis att relaxeras om inte tillräckligt många perioder hittas. Detta försöker nå det önskade minimiantalet perioder, vilket kan inkludera mindre optimala tidsfönster som bästa-pris-perioder.", + "min_periods_best": "Minsta antal bästa prisperioder att sikta på per dag. Filter kommer att relaxeras steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Uppnå Minimiantal' är aktiverad. Standard: 1", + "relaxation_attempts_best": "Hur många flexnivåer (försök) att prova innan man ger upp. Varje försök kör alla filterkombinationer på den nya flexnivån. Fler försök ökar chansen att hitta ytterligare perioder på bekostnad av längre behandlingstid." } } }, - "submit": "Fortsätt →" + "submit": "↩ Spara & tillbaka" }, "peak_price": { - "title": "🔴 Topppris Period Inställningar", - "description": "_{step_progress}_\n\nKonfigurera inställningar för **Topppris Period** binär sensor. Denna sensor är aktiv under perioder med de högsta elpriserna.\n\n---", + "title": "🔴 Topprisperiod-inställningar", + "description": "**Konfigurera inställningar för binärsensorn Topprisperiod. Denna sensor är aktiv under perioder med högsta elpriserna.**\n\n---", "sections": { "period_settings": { "name": "Periodinställningar", "description": "Konfigurera periodlängd och prisnivåbegränsningar.", "data": { - "peak_price_min_period_length": "Minimal periodlängd", + "peak_price_min_period_length": "Minsta Periodlängd", "peak_price_min_level": "Prisnivåfilter", - "peak_price_max_level_gap_count": "Gaptolerens" + "peak_price_max_level_gap_count": "Gaptolerans" }, "data_description": { - "peak_price_min_period_length": "Minsta varaktighet för att en period ska räknas som 'toppris'. Topppris-varningar är tillåtna för kortare perioder (minst 30 minuter jämfört med 60 minuter för bästa pris) eftersom korta dyra toppar är värda att varna om, även om de är för korta för förbruksplanering.", - "peak_price_min_level": "Visa endast topprisperioder om de innehåller intervall med prisnivåer ≥ valt värde. Till exempel måste perioden om du väljer '**Dyrt**' ha minst ett '**Dyrt**' eller '**Mycket dyrt**' intervall. Detta säkerställer att 'toppris'-perioder inte bara är relativt dyra för dagen, utan faktiskt dyra i absoluta termer (inte bara 'lite dyrare än genomsnittet på en billig dag').", - "peak_price_max_level_gap_count": "Maximalt antal på varandra följande intervaller som får avvika med exakt ett nivåsteg från det erforderliga nivået. Till exempel: med '**Dyrt**' filter och gapantal 1 accepteras sekvensen '**Dyrt**, **Dyrt**, **Normal**, **Dyrt**' (**Normal** är ett steg under **Dyrt**). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. **Obs:** Gaptoleransen kräver perioder ≥90 minuter (6 intervaller) för att detektera avvikelser effektivt. Standard: 0 (strikt filtrering, ingen tolerans)." + "peak_price_min_period_length": "Minsta varaktighet för en period för att betraktas som 'topppris'. Topprisvarningar är tillåtna för kortare perioder (30 minuters minimum jämfört med 60 minuter för bästa pris) eftersom korta dyra toppar är värda att varna om, även om de är för korta för förbrukningsplanering.", + "peak_price_min_level": "Visa endast topprisperioder om de innehåller intervall med prisnivåer ≥ valt värde. Till exempel betyder valet '**Dyrt**' att perioden måste ha minst ett '**Dyrt**' eller '**Mycket dyrt**' intervall. Detta säkerställer att 'topppris'-perioder inte bara är relativt dyra för dagen, utan faktiskt dyra i absoluta termer. Välj '**Alla**' för att visa topppriser oavsett deras absoluta prisnivå.", + "peak_price_max_level_gap_count": "Maximalt antal konsekutiva intervall som tillåts avvika med exakt ett nivåsteg från den nödvändiga nivån. Till exempel: med '**Dyrt**'-filter och gapantal 2, accepteras sekvensen '**Dyrt**, **Normalt**, **Normalt**, **Dyrt**' (**Normalt** är ett steg under **Dyrt**). Detta förhindrar att perioder delas upp av tillfälliga nivåavvikelser. **Obs:** Gaptolerans kräver perioder ≥90 minuter (6 intervall) för att upptäcka avvikare effektivt. Standard: 0 (strikt filtrering, ingen tolerans)." } }, "flexibility_settings": { "name": "Flexibilitetsinställningar", - "description": "Konfigurera prisjämförelsekriterer och filtrering.", + "description": "Konfigurera prisjämförelsetrösklar och filtrering.", "data": { "peak_price_flex": "Flexibilitet", "peak_price_min_distance_from_avg": "Minimiavstånd" }, "data_description": { - "peak_price_flex": "Maximalt under dagens maximumpris där intervaller fortfarande kvalificerar som 'toppris'. Rekommendation: -15 till -20 med avslappning aktiverad (standard), eller -25 till -35 utan avslappning. Maximum: -50 (hård gräns för tillförlitlig periodigkänning). Observera: Negativa värden anger avstånd under maximum.", - "peak_price_min_distance_from_avg": "Säkerställer att perioder är betydligt dyrare än dagsgenomsnittet, inte bara marginellt över det. Detta filtrerar brus och förhindrar att något-över-genomsnittet perioder markeras som 'toppris' på dagar med platta priser. Högre värden = striktare filtrering (endast riktigt dyra perioder kvalificerar). Standard: 5 betyder att perioder måste vara minst 5% över dagsgenomsnittet." + "peak_price_flex": "Maximum under dagens maximipris som intervall kan vara och fortfarande kvalificera som 'topppris'. Rekommenderat: -15 till -20 med relaxation aktiverad (standard), eller -25 till -35 utan relaxation. Maximum: -50 (hård gräns för pålitlig perioddetektering). Obs: Negativa värden indikerar avstånd under maximum.", + "peak_price_min_distance_from_avg": "Säkerställer att perioder är betydligt dyrare än dagsgenomsnittet, inte bara marginellt över det. Detta filtrerar bort brus och förhindrar att perioder strax över genomsnittet markeras som 'topppris' på dagar med platta priser. Högre värden = striktare filtrering (endast verkligt dyra perioder kvalificerar). Standard: 5 betyder att perioder måste vara minst 5% över dagsgenomsnittet." } }, "relaxation_and_target_periods": { - "name": "Avslappning & målperioder", - "description": "Konfigurera automatisk filteravslappning och målperioder. Aktivera 'Försök uppnå minsta antal perioder' för att aktivera avslappning.", + "name": "Relaxation & Målperioder", + "description": "Konfigurera automatisk filterrelaxation och målperiodsantal. Aktivera 'Uppnå Minimiantal' för att aktivera relaxation.", "data": { - "enable_min_periods_peak": "Försök uppnå minsta antal perioder", - "min_periods_peak": "Minsta antal perioder", - "relaxation_attempts_peak": "Antal avslappningsförsök" + "enable_min_periods_peak": "Uppnå Minimiantal", + "min_periods_peak": "Minsta Perioder", + "relaxation_attempts_peak": "Relaxationsförsök" }, "data_description": { - "enable_min_periods_peak": "När aktiverad kommer filtren att gradvis luckras upp om inte tillräckligt många perioder hittas. Detta försöker uppnå det önskade minsta antalet perioder för att säkerställa att du blir varnad för dyra perioder även på dagar med ovanliga prismönster.", - "min_periods_peak": "Minsta antal topprisperioder att sträva efter per dag. Filtren kommer att luckras upp steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Försök uppnå minsta antal perioder' är aktiverad. Standard: 1", - "relaxation_attempts_peak": "Hur många gånger avslappningslogiken får försöka hitta nya kombinationer av flex och filter. Öka detta när topperioderna är svåra att hitta på grund av platta eller mycket volatila dagar. Fler försök ger större chans att hitta perioder men kräver lite mer beräkningstid." + "enable_min_periods_peak": "När aktiverad kommer filter gradvis att relaxeras om inte tillräckligt många perioder hittas. Detta försöker nå det önskade minimiantalet perioder för att säkerställa att du varnas om dyra perioder även på dagar med ovanliga prismönster.", + "min_periods_peak": "Minsta antal topprisperioder att sikta på per dag. Filter kommer att relaxeras steg för steg för att försöka uppnå detta antal. Endast aktiv när 'Uppnå Minimiantal' är aktiverad. Standard: 1", + "relaxation_attempts_peak": "Hur många flexnivåer (försök) att prova innan man ger upp. Varje försök kör alla filterkombinationer på den nya flexnivån. Fler försök ökar chansen att hitta ytterligare toppperioder på bekostnad av längre behandlingstid." } } }, - "submit": "Fortsätt →" + "submit": "↩ Spara & tillbaka" }, "price_trend": { "title": "📈 Pristrendtrösklar", - "description": "_{step_progress}_\n\n**Konfigurera trösklar för pristrendsensorer. Dessa sensorer jämför det aktuella priset med genomsnittet av de nästa N timmarna för att avgöra om priserna stiger, faller eller är stabila.**\n\n---", - "sections": { - "price_trend_thresholds": { - "name": "Pristrendtrösklar", - "description": "Definiera pristrendnivåer.", - "data": { - "price_trend_threshold_rising": "Stigande tröskel", - "price_trend_threshold_falling": "Fallande tröskel" - }, - "data_description": { - "price_trend_threshold_rising": "Procent för genomsnittlig prisökning per timme som kvalificerar trenden som 'stigande'. Exempel: 5 betyder minst 5% ökning per timme. Sensorer med denna trend indikerar att priserna kommer att stiga snabbt. Standard: 5%", - "price_trend_threshold_falling": "Procent för genomsnittlig prisminskning per timme som kvalificerar trenden som 'fallande'. Exempel: -5 betyder minst 5% minskning per timme. Sensorer med denna trend indikerar att priserna kommer att falla snabbt. Standard: -5%" - } - } + "description": "**Konfigurera tröskelvärden för pristrendsensorer. Dessa sensorer jämför aktuellt pris med genomsnittet av de nästa N timmarna för att bestämma om priserna stiger, faller eller är stabila.**", + "data": { + "price_trend_threshold_rising": "Stigande tröskel", + "price_trend_threshold_falling": "Fallande tröskel" }, - "submit": "Fortsätt →" + "data_description": { + "price_trend_threshold_rising": "Procentandel som genomsnittet av de nästa N timmarna måste vara över det aktuella priset för att kvalificera som 'stigande' trend. Exempel: 5 betyder att genomsnittet är minst 5% högre → priserna kommer att stiga. Typiska värden: 5-15%. Standard: 5%", + "price_trend_threshold_falling": "Procentandel (negativ) som genomsnittet av de nästa N timmarna måste vara under det aktuella priset för att kvalificera som 'fallande' trend. Exempel: -5 betyder att genomsnittet är minst 5% lägre → priserna kommer att falla. Typiska värden: -5 till -15%. Standard: -5%" + }, + "submit": "↩ Spara & tillbaka" }, "volatility": { - "title": "💨 Volatilitetströsklar", - "description": "_{step_progress}_\n\n**Konfigurera trösklar för volatilitetsklassificering. Volatilitet mäter relativ prisvariation med hjälp av variationskoefficienten (VK = standardavvikelse / medelvärde × 100%). Dessa trösklar är procentvärden som fungerar över alla prisnivåer.**\n\nAnvänds av:\n• Volatilitetssensorer (klassificering)\n• Trendsensorer (adaptiv tröskel justering: <måttlig = mer känslig, ≥hög = mindre känslig)\n\n---", - "sections": { - "volatility_thresholds": { - "name": "Volatilitetströsklar", - "description": "Definiera volatilitetsklassificeringsignivåer.", - "data": { - "volatility_threshold_moderate": "Måttlig tröskel", - "volatility_threshold_high": "Hög tröskel", - "volatility_threshold_very_high": "Mycket hög tröskel" - }, - "data_description": { - "volatility_threshold_moderate": "Gränsvärde för standardavvikelse (% av genomsnitt) för att klassificera prisvariation som 'måttlig'. Exempel: 10 betyder standardavvikelse ≥ 10% av genomsnitt. Detta indikerar ökad prisinstabilitet. Standard: 10%", - "volatility_threshold_high": "Gränsvärde för standardavvikelse (% av genomsnitt) för att klassificera prisvariation som 'hög'. Exempel: 20 betyder standardavvikelse ≥ 20% av genomsnitt. Detta indikerar betydande prissvingningar. Standard: 20%", - "volatility_threshold_very_high": "Gränsvärde för standardavvikelse (% av genomsnitt) för att klassificera prisvariation som 'mycket hög'. Exempel: 30 betyder standardavvikelse ≥ 30% av genomsnitt. Detta indikerar extrem prisinstabilitet. Standard: 30%" - } - } + "title": "💨 Prisvolatilitetströsklar", + "description": "**Konfigurera tröskelvärden för volatilitetsklassificering.** Volatilitet mäter relativ prisvariation med variationskoefficienten (CV = standardavvikelse / medelvärde × 100%). Dessa tröskelvärden är procentvärden som fungerar över alla prisnivåer.\n\nAnvänds av:\n• Volatilitetssensorer (klassificering)\n• Trendsensorer (adaptiv tröskeljustering: <måttlig = mer känslig, ≥hög = mindre känslig)", + "data": { + "volatility_threshold_moderate": "Måttlig tröskel", + "volatility_threshold_high": "Hög tröskel", + "volatility_threshold_very_high": "Mycket hög tröskel" }, - "submit": "Fortsätt →" + "data_description": { + "volatility_threshold_moderate": "Variationskoefficient (CV) vid vilken priser anses 'måttligt volatila'. CV = (standardavvikelse / medelvärde) × 100%. Exempel: 15 betyder prisfluktuationer på ±15% runt genomsnittet. Sensorer visar denna klassificering, trendsensorer blir mer känsliga. Standard: 15%", + "volatility_threshold_high": "Variationskoefficient (CV) vid vilken priser anses 'högvolatila'. Exempel: 30 betyder prisfluktuationer på ±30% runt genomsnittet. Större prishopp förväntas, trendsensorer blir mindre känsliga. Standard: 30%", + "volatility_threshold_very_high": "Variationskoefficient (CV) vid vilken priser anses 'mycket högvolatila'. Exempel: 50 betyder extrema prisfluktuationer på ±50% runt genomsnittet. På sådana dagar är starka pristoppar troliga. Standard: 50%" + }, + "submit": "↩ Spara & tillbaka" }, "chart_data_export": { - "title": "📊 Diagram-dataexport Sensor", - "description": "_{step_progress}_\n\nDiagram-dataexport-sensorn tillhandahåller prisdata som sensorattribut.\n\n⚠️ **Observera:** Denna sensor är en legacy-funktion för kompatibilitet med äldre verktyg.\n\n**Rekommenderat för nya installationer:** Använd `tibber_prices.get_chartdata` **tjänsten direkt** - den är mer flexibel, effektiv och det moderna Home Assistant-tillvägagångssättet.\n\n**När denna sensor är meningsfull:**\n\n✅ Ditt dashboardverktyg kan **endast** läsa attribut (inga tjänsteanrop)\n✅ Du behöver statiska data som uppdateras automatiskt\n❌ **Inte för automationer:** Använd `tibber_prices.get_chartdata` direkt där - mer flexibel och effektiv!\n\n---\n\n**Aktivera sensorn:**\n\n1. Öppna **Inställningar → Enheter och tjänster → Tibber Prices**\n2. Välj ditt hem → Hitta **'Diagramdataexport'** (Diagnostik-sektionen)\n3. **Aktivera sensorn** (inaktiverad som standard)\n\n**Konfiguration (valfritt):**\n\nStandardinställningar fungerar direkt (idag+imorgon, 15-minuters intervaller, endast priser).\n\nFör anpassning, lägg till i **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alla parametrar:** Se `tibber_prices.get_chartdata` tjänstdokumentation", - "submit": "Slutför ✓" + "title": "📊 Diagramdataexport-sensor", + "description": "Diagramdataexport-sensorn tillhandahåller prisdata som sensorattribut.\n\n⚠️ **Obs:** Denna sensor är en äldre funktion för kompatibilitet med äldre verktyg.\n\n**Rekommenderat för nya konfigurationer:** Använd `tibber_prices.get_chartdata` **tjänsten direkt** - den är mer flexibel, effektiv och det moderna Home Assistant-sättet.\n\n**När denna sensor är meningsfull:**\n\n✅ Ditt instrumentpanelverktyg kan **endast** läsa attribut (inga tjänsteanrop)\n✅ Du behöver statisk data som uppdateras automatiskt\n❌ **Inte för automationer:** Använd `tibber_prices.get_chartdata` direkt där - mer flexibelt och effektivt!\n\n---\n\n**Aktivera sensorn:**\n\n1. Öppna **Inställningar → Enheter & Tjänster → Tibber-priser**\n2. Välj ditt hem → Hitta **'Diagramdataexport'** (Diagnostiksektion)\n3. **Aktivera sensorn** (inaktiverad som standard)\n\n**Konfiguration (valfritt):**\n\nStandardinställningar fungerar direkt (idag+imorgon, 15-minutersintervall, endast priser).\n\nFör anpassning, lägg till i **`configuration.yaml`**:\n\n```yaml\ntibber_prices:\n chart_export:\n day:\n - today\n - tomorrow\n include_level: true\n include_rating_level: true\n```\n\n**Alla parametrar:** Se `tibber_prices.get_chartdata` tjänstdokumentation", + "submit": "↩ Ok & tillbaka" + }, + "reset_to_defaults": { + "title": "🔄 Återställ till standard", + "description": "⚠️ **Varning:** Detta kommer att återställa **ALLA** inställningar till fabriksstandard.\n\n**Vad som kommer att återställas:**\n• Alla prisbetygströsklar\n• Alla volatilitetströsklar\n• Alla pristrendtrösklar\n• Alla bästa prisperiodinställningar\n• Alla topprisperiodinställningar\n• Visningsinställningar\n• Allmänna inställningar\n\n**Vad som INTE kommer att återställas:**\n• Din Tibber API-token\n• Valt hem\n• Valuta\n\n**💡 Tips:** Detta är användbart om du vill börja om från början efter att ha experimenterat med inställningar.", + "data": { + "confirm_reset": "Ja, återställ allt till standard" + }, + "submit": "Återställ nu" } }, "error": { "auth": "Tibber-åtkomsttoken är ogiltig.", - "connection": "Kunde inte ansluta till Tibber. Vänligen kontrollera din internetanslutning.", - "unknown": "Ett oväntat fel inträffade. Vänligen kontrollera loggarna för detaljer.", - "cannot_connect": "Kunde inte ansluta", + "connection": "Kan inte ansluta till Tibber. Kontrollera din internetanslutning.", + "unknown": "Ett oväntat fel inträffade. Kontrollera loggarna för detaljer.", + "cannot_connect": "Anslutning misslyckades", "invalid_access_token": "Ogiltig åtkomsttoken", - "different_home": "Åtkomsttoken är inte giltig för hem-ID:t som denna integration är konfigurerad för.", + "different_home": "Åtkomsttoken är inte giltig för det hem-ID som denna integration är konfigurerad för.", + "invalid_period_length": "Periodlängd måste vara minst 15 minuter (multiplar av 15).", "invalid_flex": "Flexibilitetsprocent måste vara mellan -50% och +50%", - "invalid_best_price_distance": "Avståndsprocent måste vara mellan -50% och 0% (negativ = under genomsnitt)", - "invalid_peak_price_distance": "Avståndsprocent måste vara mellan 0% och 50% (positiv = över genomsnitt)", - "invalid_min_periods": "Minsta antal perioder måste vara mellan 1 och 10", - "invalid_period_length": "Periodlängden måste vara minst 15 minuter (multiplar av 15).", - "invalid_gap_count": "Gaptolerans måste vara mellan 0 och 8", - "invalid_relaxation_attempts": "Avslappningsförsök måste vara mellan 1 och 12", - "invalid_price_rating_low": "Låg prisklassificeringströskel måste vara mellan -50% och -5%", - "invalid_price_rating_high": "Hög prisklassificeringströskel måste vara mellan 5% och 50%", + "invalid_best_price_distance": "Avstånds procent måste vara mellan -50% och 0% (negativ = under genomsnitt)", + "invalid_peak_price_distance": "Avstånds procent måste vara mellan 0% och 50% (positiv = över genomsnitt)", + "invalid_min_periods": "Minsta perioder måste vara mellan 1 och 10", + "invalid_gap_count": "Gapantal måste vara mellan 0 och 8", + "invalid_relaxation_attempts": "Relaxationsförsök måste vara mellan 1 och 12", + "invalid_price_rating_low": "Låg prisbetygströskel måste vara mellan -50% och -5%", + "invalid_price_rating_high": "Hög prisbetygströskel måste vara mellan 5% och 50%", "invalid_price_rating_thresholds": "Låg tröskel måste vara mindre än hög tröskel", "invalid_volatility_threshold_moderate": "Måttlig volatilitetströskel måste vara mellan 5% och 25%", "invalid_volatility_threshold_high": "Hög volatilitetströskel måste vara mellan 20% och 40%", @@ -337,16 +343,19 @@ "invalid_price_trend_falling": "Fallande trendtröskel måste vara mellan -50% och -1%" }, "abort": { - "entry_not_found": "Tibber-konfigurationspost hittades inte." + "entry_not_found": "Tibber-konfigurationspost hittades inte.", + "reset_cancelled": "Återställning avbruten. Inga ändringar gjordes i din konfiguration.", + "reset_successful": "✅ Alla inställningar har återställts till fabriksstandard. Din konfiguration är nu som en ny installation.", + "finished": "Konfiguration slutförd." } }, "entity": { "sensor": { "current_interval_price": { - "name": "Nuvarande elpris" + "name": "Aktuellt elpris" }, "current_interval_price_base": { - "name": "Nuvarande elpris (Energipanel)" + "name": "Aktuellt elpris (Energidashboard)" }, "next_interval_price": { "name": "Nästa elpris" @@ -355,17 +364,17 @@ "name": "Föregående elpris" }, "current_hour_average_price": { - "name": "⌀ Timpris nuvarande" + "name": "⌀ Timpris aktuell" }, "next_hour_average_price": { "name": "⌀ Timpris nästa" }, "current_interval_price_level": { - "name": "Nuvarande prisnivå", + "name": "Aktuell prisnivå", "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", - "normal": "Normal", + "normal": "Normalt", "expensive": "Dyrt", "very_expensive": "Mycket dyrt" } @@ -375,7 +384,7 @@ "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", - "normal": "Normal", + "normal": "Normalt", "expensive": "Dyrt", "very_expensive": "Mycket dyrt" } @@ -385,17 +394,17 @@ "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", - "normal": "Normal", + "normal": "Normalt", "expensive": "Dyrt", "very_expensive": "Mycket dyrt" } }, "current_hour_price_level": { - "name": "Nuvarande timprisnivå", + "name": "Aktuell timprisnivå", "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", - "normal": "Normal", + "normal": "Normalt", "expensive": "Dyrt", "very_expensive": "Mycket dyrt" } @@ -405,7 +414,7 @@ "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", - "normal": "Normal", + "normal": "Normalt", "expensive": "Dyrt", "very_expensive": "Mycket dyrt" } @@ -429,7 +438,7 @@ "name": "⌀ Pris imorgon" }, "yesterday_price_level": { - "name": "Prisnivå igår", + "name": "Gårdagens prisnivå", "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", @@ -439,7 +448,7 @@ } }, "today_price_level": { - "name": "Prisnivå idag", + "name": "Dagens prisnivå", "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", @@ -449,7 +458,7 @@ } }, "tomorrow_price_level": { - "name": "Prisnivå imorgon", + "name": "Morgondagens prisnivå", "state": { "very_cheap": "Mycket billigt", "cheap": "Billigt", @@ -459,7 +468,7 @@ } }, "yesterday_price_rating": { - "name": "Prisvärdering igår", + "name": "Gårdagens prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -467,7 +476,7 @@ } }, "today_price_rating": { - "name": "Prisvärdering idag", + "name": "Dagens prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -475,7 +484,7 @@ } }, "tomorrow_price_rating": { - "name": "Prisvärdering imorgon", + "name": "Morgondagens prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -483,25 +492,25 @@ } }, "trailing_price_average": { - "name": "⌀ Pris rullande 24t" + "name": "⌀ Pris glidande 24h" }, "leading_price_average": { - "name": "⌀ Pris framåtblickande 24t" + "name": "⌀ Pris framåt 24h" }, "trailing_price_min": { - "name": "Rullande 24t minimumpris" + "name": "Glidande 24h minimipris" }, "trailing_price_max": { - "name": "Rullande 24t maximumpris" + "name": "Glidande 24h maximipris" }, "leading_price_min": { - "name": "Framåtblickande 24t minimumpris" + "name": "Framåt 24h minimipris" }, "leading_price_max": { - "name": "Framåtblickande 24t maximumpris" + "name": "Framåt 24h maximipris" }, "current_interval_price_rating": { - "name": "Nuvarande prisvärdering", + "name": "Aktuellt prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -509,7 +518,7 @@ } }, "next_interval_price_rating": { - "name": "Nästa prisvärdering", + "name": "Nästa prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -517,7 +526,7 @@ } }, "previous_interval_price_rating": { - "name": "Föregående prisvärdering", + "name": "Föregående prisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -525,7 +534,7 @@ } }, "current_hour_price_rating": { - "name": "Nuvarande timprisvärdering", + "name": "Aktuellt timprisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -533,7 +542,7 @@ } }, "next_hour_price_rating": { - "name": "Nästa timprisvärdering", + "name": "Nästa timprisbetyg", "state": { "low": "Låg", "normal": "Normal", @@ -541,31 +550,31 @@ } }, "next_avg_1h": { - "name": "⌀ Pris nästa 1t" + "name": "⌀ Pris nästa 1h" }, "next_avg_2h": { - "name": "⌀ Pris nästa 2t" + "name": "⌀ Pris nästa 2h" }, "next_avg_3h": { - "name": "⌀ Pris nästa 3t" + "name": "⌀ Pris nästa 3h" }, "next_avg_4h": { - "name": "⌀ Pris nästa 4t" + "name": "⌀ Pris nästa 4h" }, "next_avg_5h": { - "name": "⌀ Pris nästa 5t" + "name": "⌀ Pris nästa 5h" }, "next_avg_6h": { - "name": "⌀ Pris nästa 6t" + "name": "⌀ Pris nästa 6h" }, "next_avg_8h": { - "name": "⌀ Pris nästa 8t" + "name": "⌀ Pris nästa 8h" }, "next_avg_12h": { - "name": "⌀ Pris nästa 12t" + "name": "⌀ Pris nästa 12h" }, "price_trend_1h": { - "name": "Pristrend (1t)", + "name": "Pristrend (1h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -573,7 +582,7 @@ } }, "price_trend_2h": { - "name": "Pristrend (2t)", + "name": "Pristrend (2h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -581,7 +590,7 @@ } }, "price_trend_3h": { - "name": "Pristrend (3t)", + "name": "Pristrend (3h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -589,7 +598,7 @@ } }, "price_trend_4h": { - "name": "Pristrend (4t)", + "name": "Pristrend (4h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -597,7 +606,7 @@ } }, "price_trend_5h": { - "name": "Pristrend (5t)", + "name": "Pristrend (5h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -605,7 +614,7 @@ } }, "price_trend_6h": { - "name": "Pristrend (6t)", + "name": "Pristrend (6h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -613,7 +622,7 @@ } }, "price_trend_8h": { - "name": "Pristrend (8t)", + "name": "Pristrend (8h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -621,7 +630,7 @@ } }, "price_trend_12h": { - "name": "Pristrend (12t)", + "name": "Pristrend (12h)", "state": { "rising": "Stigande", "falling": "Fallande", @@ -629,7 +638,7 @@ } }, "current_price_trend": { - "name": "Nuvarande pristrend", + "name": "Aktuell pristrend", "state": { "rising": "Stigande", "falling": "Fallande", @@ -637,59 +646,59 @@ } }, "next_price_trend_change": { - "name": "Nästa trendförändring" + "name": "Nästa pristrendändring" }, "daily_rating": { - "name": "Daglig prisvärdering" + "name": "Dagligt prisbetyg" }, "monthly_rating": { - "name": "Månatlig prisvärdering" + "name": "Månatligt prisbetyg" }, "data_lifecycle_status": { - "name": "Datalivscykel-status", + "name": "Datalivscykelstatus", "state": { "cached": "Cachad", "fresh": "Färsk", "refreshing": "Uppdaterar", - "searching_tomorrow": "Söker morgondagens data", - "turnover_pending": "Midnattskifte väntar", + "searching_tomorrow": "Söker morgondag", + "turnover_pending": "Väntar på övergång", "error": "Fel" } }, "today_volatility": { - "name": "Volatilitet idag", + "name": "Dagens prisvolatilitet", "state": { "low": "Låg", "moderate": "Måttlig", "high": "Hög", - "very_high": "Mycket Hög" + "very_high": "Mycket hög" } }, "tomorrow_volatility": { - "name": "Volatilitet imorgon", + "name": "Morgondagens prisvolatilitet", "state": { "low": "Låg", "moderate": "Måttlig", "high": "Hög", - "very_high": "Mycket Hög" + "very_high": "Mycket hög" } }, "next_24h_volatility": { - "name": "Volatilitet nästa 24t", + "name": "Nästa 24h prisvolatilitet", "state": { "low": "Låg", "moderate": "Måttlig", "high": "Hög", - "very_high": "Mycket Hög" + "very_high": "Mycket hög" } }, "today_tomorrow_volatility": { - "name": "Volatilitet idag+imorgon", + "name": "Idag+Imorgon prisvolatilitet", "state": { "low": "Låg", "moderate": "Måttlig", "high": "Hög", - "very_high": "Mycket Hög" + "very_high": "Mycket hög" } }, "best_price_end_time": { @@ -729,7 +738,7 @@ "name": "Topppris startar om" }, "home_type": { - "name": "Bostadstyp", + "name": "Hemtyp", "state": { "apartment": "Lägenhet", "rowhouse": "Radhus", @@ -738,10 +747,10 @@ } }, "home_size": { - "name": "Boyta" + "name": "Hemstorlek" }, "main_fuse_size": { - "name": "Huvudsäkring" + "name": "Huvudsäkringsstorlek" }, "number_of_residents": { "name": "Antal boende" @@ -749,14 +758,14 @@ "primary_heating_source": { "name": "Primär värmekälla", "state": { - "air2air_heatpump": "Luft-till-luft-värmepump", - "air2water_heatpump": "Luft-till-vatten-värmepump", - "boiler": "Varmvattenberedare", + "air2air_heatpump": "Luft-till-luft värmepump", + "air2water_heatpump": "Luft-till-vatten värmepump", + "boiler": "Panna", "central_heating": "Centralvärme", "district_heating": "Fjärrvärme", "district": "Fjärrvärme", - "electric_boiler": "Elektrisk varmvattenberedare", - "electricity": "Elektricitet", + "electric_boiler": "Elpanna", + "electricity": "El", "floor": "Golvvärme", "gas": "Gas", "ground_heatpump": "Bergvärmepump", @@ -788,21 +797,21 @@ "name": "Momstyp" }, "estimated_annual_consumption": { - "name": "Uppskattat årsförbrukning" + "name": "Beräknad årlig förbrukning" }, "subscription_status": { "name": "Abonnemangsstatus", "state": { "running": "Aktiv", "ended": "Avslutad", - "pending": "Väntar", + "pending": "Väntande", "unknown": "Okänd" } }, "chart_data_export": { "name": "Diagramdataexport", "state": { - "pending": "Väntar", + "pending": "Väntande", "ready": "Redo", "error": "Fel" } @@ -810,7 +819,7 @@ "chart_metadata": { "name": "Diagrammetadata", "state": { - "pending": "Väntar", + "pending": "Väntande", "ready": "Redo", "error": "Fel" } @@ -818,10 +827,10 @@ }, "binary_sensor": { "peak_price_period": { - "name": "Toppris-period" + "name": "Topprisperiod" }, "best_price_period": { - "name": "Bästa pris-period" + "name": "Bästa Prisperiod" }, "connection": { "name": "Tibber API-anslutning" @@ -840,81 +849,81 @@ "issues": { "new_homes_available": { "title": "Nya Tibber-hem upptäckta", - "description": "Vi upptäckte {count} nytt/nya hem på ditt Tibber-konto: {homes}. Du kan lägga till dem i Home Assistant via Tibber-integrationskonfigurationen." + "description": "Vi upptäckte {count} nytt/nya hem på ditt Tibber-konto: {homes}. Du kan lägga till dem i Home Assistant genom Tibber-integrationskonfigurationen." }, "homes_removed": { "title": "Tibber-hem borttagna", - "description": "Vi upptäckte att {count} hem har tagits bort från ditt Tibber-konto: {homes}. Vänligen granska din Tibber-integrationskonfiguration." + "description": "Vi upptäckte att {count} hem har tagits bort från ditt Tibber-konto: {homes}. Granska din Tibber-integrationskonfiguration." }, "tomorrow_data_missing": { - "title": "Prisdata för imorgon saknas för {home_name}", - "description": "Elprisdata för imorgon är fortfarande otillgänglig efter {warning_hour}:00. Detta är ovanligt, eftersom Tibber vanligtvis publicerar morgondagens priser på eftermiddagen (runt 13:00-14:00 CET).\n\nMöjliga orsaker:\n- Tibber har inte publicerat morgondagens priser ännu\n- Tillfälliga API-problem\n- Din elleverantör har inte skickat in priser till Tibber\n\nDetta problem kommer att lösas automatiskt när morgondagens data blir tillgänglig. Om detta fortsätter efter 20:00, vänligen kontrollera Tibber-appen eller kontakta Tibber-support." + "title": "Morgondagens prisdata saknas för {home_name}", + "description": "Morgondagens elprisdata är fortfarande otillgänglig efter {warning_hour}:00. Detta är ovanligt, eftersom Tibber vanligtvis publicerar morgondagens priser på eftermiddagen (runt 13:00-14:00 CET).\n\nMöjliga orsaker:\n- Tibber har ännu inte publicerat morgondagens priser\n- Tillfälliga API-problem\n- Din elleverantör har inte skickat priser till Tibber\n\nDetta problem löser sig automatiskt när morgondagens data blir tillgänglig. Om detta kvarstår efter 20:00, kontrollera Tibber-appen eller kontakta Tibber support." }, "rate_limit_exceeded": { "title": "API-hastighetsgräns överskriden för {home_name}", - "description": "Tibber-API:et har hastighetsbegränsat denna integration efter {error_count} på varandra följande fel. Detta betyder att förfrågningar görs för ofta.\n\nIntegrationen kommer automatiskt att försöka igen med ökande fördröjningar. Detta problem kommer att lösas när hastighetsgränsen löper ut.\n\nOm detta fortsätter i flera timmar, överväg:\n- Att kontrollera om flera Home Assistant-instanser använder samma API-token\n- Att verifiera att inga andra applikationer använder din Tibber-API-token mycket\n- Att minska uppdateringsfrekvensen om du har anpassat den" + "description": "Tibber API har hastighetsbegränsat denna integration efter {error_count} konsekutiva fel. Detta betyder att förfrågningar görs för ofta.\n\nIntegrationen kommer automatiskt att försöka igen med ökande fördröjningar. Detta problem löser sig när hastighetsgränsen löper ut.\n\nOm detta kvarstår i flera timmar, överväg:\n- Kontrollera om flera Home Assistant-instanser använder samma API-token\n- Verifiera att inga andra applikationer använder din Tibber API-token kraftigt\n- Minska uppdateringsfrekvensen om du har anpassat den" }, "home_not_found": { - "title": "Hemmet {home_name} hittades inte i Tibber-kontot", - "description": "Hemmet som konfigurerats i denna integration (post-ID: {entry_id}) är inte längre tillgängligt i ditt Tibber-konto. Detta händer vanligtvis när:\n- Hemmet togs bort från ditt Tibber-konto\n- Hemmet flyttades till ett annat Tibber-konto\n- Åtkomst till detta hem återkallades\n\nVänligen ta bort denna integrationspost och lägg till den igen om hemmet fortfarande ska övervakas. För att ta bort denna post, gå till Inställningar → Enheter och tjänster → Tibber Prices och ta bort {home_name}-konfigurationen." + "title": "Hem {home_name} hittades inte i Tibber-konto", + "description": "Hemmet som konfigurerats i denna integration (post-ID: {entry_id}) är inte längre tillgängligt i ditt Tibber-konto. Detta händer vanligtvis när:\n- Hemmet togs bort från ditt Tibber-konto\n- Hemmet flyttades till ett annat Tibber-konto\n- Åtkomst till detta hem återkallades\n\nTa bort denna integrationspost och lägg till den igen om hemmet fortfarande ska övervakas. För att ta bort denna post, gå till Inställningar → Enheter & Tjänster → Tibber-priser och radera {home_name}-konfigurationen." } }, "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.", + "description": "Hämta prisdata för ett specifikt tidsintervall med automatisk routing. Utvecklings- och testservice för price_info_for_range API-funktionen. Använder automatiskt PRICE_INFO, PRICE_INFO_RANGE eller båda baserat på tidsintervallets gränser.", "fields": { "entry_id": { - "name": "Post-ID", - "description": "Konfigurationspost-ID för Tibber-integrationen." + "name": "Entry-ID", + "description": "Config entry-ID för Tibber-integrationen." }, "start_time": { "name": "Starttid", - "description": "Start av tidsintervallet (inklusiv, tidszonskänslig)." + "description": "Start för tidsintervallet (inklusivt, tidszonmedveten)." }, "end_time": { "name": "Sluttid", - "description": "Slut av tidsintervallet (exklusiv, tidszonskänslig)." + "description": "Slut för tidsintervallet (exklusivt, tidszonmedveten)." } } }, "get_apexcharts_yaml": { - "name": "Hämta ApexCharts-kort YAML", - "description": "⚠️ VIKTIGT: Denna tjänst genererar en GRUNDLÄGGANDE EXEMPEL-konfiguration för ApexCharts-kort som utgångspunkt. Det är INTE en komplett lösning för alla ApexCharts-funktioner. Denna integration är i första hand en DATALEVERANTÖR. Den genererade YAML demonstrerar hur du använder `get_chartdata`-tjänsten för att hämta prisdata. På grund av den segmenterade naturen hos våra data (olika tidsperioder per serie) och användningen av Home Assistants service-API istället för entitetsattribut, är många avancerade ApexCharts-funktioner (som in_header, vissa transformationer) inte kompatibla eller kräver manuell anpassning. Du är välkommen att anpassa den genererade YAML för dina specifika behov, men förstå att omfattande ApexCharts-konfigurationsstöd ligger utanför ramen för denna integration. Gemenskapsbidrag med förbättrade konfigurationer är alltid uppskattade - om du hittar en bättre inställning som fungerar, dela den gärna så att alla kan dra nytta av det! För direkt dataåtkomst för att bygga dina egna diagram, använd `get_chartdata`-tjänsten istället.", + "name": "Hämta ApexCharts Card YAML", + "description": "⚠️ VIKTIGT: Denna service genererar en GRUNDLÄGGANDE EXEMPELKONFIGURATION för ApexCharts Card som en startpunkt. Det är INTE en komplett lösning för alla ApexCharts-funktioner. Denna integration är primärt en DATALEVERANTÖR. Den genererade YAML:en demonstrerar hur man använder `get_chartdata`-servicen för att hämta prisdata. På grund av den segmenterade naturen hos vår data (olika tidsperioder per serie) och användningen av Home Assistants service-API istället för entitetsattribut är många avancerade ApexCharts-funktioner (som in_header, vissa transformationer) inte kompatibla eller kräver manuell anpassning. Du är välkommen att anpassa den genererade YAML:en för dina specifika behov, men förstå att omfattande ApexCharts-konfigurationsstöd ligger utanför denna integrations omfång. Community-bidrag med förbättrade konfigurationer uppskattas alltid - om du hittar en bättre konfiguration som fungerar, dela den så att alla kan dra nytta av den! För direkt dataåtkomst för att bygga dina egna diagram, använd `get_chartdata`-servicen istället.", "fields": { "entry_id": { - "name": "Post-ID", - "description": "Konfigurationspost-ID för Tibber-integrationen." + "name": "Entry-ID", + "description": "Config entry-ID för Tibber-integrationen." }, "day": { "name": "Dag", - "description": "Vilken dag som ska visualiseras (standard: Rullande fönster). Fasta dagalternativ (Igår/Idag/Imorgon) visar 24t-spann utan extra beroenden. Dynamiska alternativ kräver config-template-card: Rullande fönster skapar ett fast 48t-fönster som automatiskt växlar mellan igår+idag och idag+imorgon baserat på datatillgänglighet. Rullande fönster (Auto-Zoom) beter sig likadant men zoomar dessutom automatiskt in (2t tillbakablick + återstående tid till midnatt, graph_span minskar varje kvart)." + "description": "Vilken dag som ska visualiseras (standard: Glidande fönster). Fasta dagalternativ (Igår/Idag/Imorgon) visar 24h-spann utan ytterligare beroenden. Dynamiska alternativ kräver config-template-card: Glidande fönster visar ett fast 48h-fönster som automatiskt skiftar mellan igår+idag och idag+imorgon baserat på datatillgänglighet. Glidande fönster (Auto-Zoom) beter sig likadant men zoomar dessutom automatiskt in (2h tillbakablick + återstående tid till midnatt, graph_span minskar var 15:e minut)." }, "level_type": { "name": "Nivåtyp", - "description": "Välj vilken prisnivåklassificering som ska visualiseras: 'rating_level' (låg/normal/hög baserat på dina konfigurerade tröskelvärden) eller 'level' (Tibber API-nivåer: mycket billig/billig/normal/dyr/mycket dyr)." + "description": "Välj vilken prisnivåklassificering som ska visualiseras: 'rating_level' (låg/normal/hög baserat på dina konfigurerade tröskelvärden) eller 'level' (Tibber API-nivåer: mycket billigt/billigt/normalt/dyrt/mycket dyrt)." }, "highlight_best_price": { "name": "Markera bästa prisperioder", - "description": "Lägg till ett halvgenomskinligt grönt överlägg för att markera de bästa prisperioderna i diagrammet. Detta gör det enkelt att visuellt identifiera de optimala tiderna för energiförbrukning." + "description": "Lägg till ett halvtransparent grönt överlag för att markera de bästa prisperioderna i diagrammet. Detta gör det enkelt att visuellt identifiera de optimala tiderna för energiförbrukning." } } }, "get_chartdata": { "name": "Hämta diagramdata", - "description": "Returnerar prisdata i ett enkelt diagramvänligt format kompatibelt med Tibber Core-integrationens outputstruktur. Perfekt för användning med populära diagramkort som ha-price-timeline-card, ApexCharts Card, Plotly Graph Card, Mini Graph Card eller den inbyggda History Graph Card. Fältnamn och datastruktur kan anpassas för att matcha diagrammets krav.", + "description": "Returnerar prisdata i ett enkelt diagramvänligt format som är kompatibelt med Tibber Core-integrationens utdatastruktur. Perfekt för användning med populära diagramkort som ha-price-timeline-card, ApexCharts Card, Plotly Graph Card, Mini Graph Card eller det inbyggda History Graph Card. Fältnamn och datastruktur kan anpassas för att matcha dina specifika diagramkrav.", "sections": { "general": { "name": "Allmänt", - "description": "Grundalternativ för hämtning av diagramdata." + "description": "Allmänna inställningar för att hämta diagramdata." }, "selection": { - "name": "Val", - "description": "Välj vilka data som ska inkluderas i utdatan." + "name": "Urval", + "description": "Välj vilken data som ska inkluderas i utdatan." }, "filters": { "name": "Filter", - "description": "Filtrera data baserat på prisnivåer, prisvärderingar eller speciella perioder." + "description": "Filtrera data baserat på prisnivåer, betygsnivåer eller specialperioder." }, "transformation": { "name": "Transformera data", @@ -925,110 +934,110 @@ "description": "Anpassa utdataformatet." }, "arrays_of_arrays": { - "name": "Avancerade utdatainställningar: Array av arrayer", - "description": "Inställningar för utdataformat vid användning av array av arrayer." + "name": "Avancerade utdatainställningar: Array av arrays", + "description": "Inställningar för utdataformat när en array av arrays används." }, "arrays_of_objects": { "name": "Avancerade utdatainställningar: Array av objekt", - "description": "Inställningar för utdataformat vid användning av array av objekt." + "description": "Inställningar för utdataformat när en array av objekt används." } }, "fields": { "entry_id": { - "name": "Post-ID", - "description": "Konfigurationspost-ID för Tibber-integrationen." + "name": "Entry-ID", + "description": "Config entry-ID för Tibber-integrationen." }, "day": { "name": "Dag", - "description": "Vilken dag(ar) ska priser hämtas för. Du kan välja flera dagar. Om inte angivet, returneras ett rullande 2-dagars fönster: idag+imorgon (när morgondagens data är tillgänglig) eller igår+idag (när morgondagens data inte är tillgänglig ännu). Detta ger kontinuerlig diagramvisning utan luckor." + "description": "Vilken/vilka dag(ar) att hämta priser för. Du kan välja flera dagar. Om inget anges returneras ett glidande 2-dagarsfönster: idag+imorgon (när morgondagens data finns tillgänglig) eller igår+idag (när morgondagens data ännu inte är tillgänglig). Detta ger kontinuerlig diagramvisning utan luckor." }, "resolution": { "name": "Upplösning", - "description": "Tidsupplösning för returnerade data. Alternativ: 'interval' (standard, 15-minuters intervaller, 96 datapunkter per dag), 'hourly' (timgenomsnitt, 24 datapunkter per dag)." + "description": "Tidsupplösning för returnerad data. Alternativ: 'interval' (standard, 15-minutersintervall, 96 punkter per dag), 'hourly' (timmedelvärden, 24 punkter per dag)." }, "output_format": { "name": "Utdataformat", - "description": "Utdataformat för returnerade data. Alternativ: 'array_of_objects' (standard, array av objekt med anpassningsbara fältnamn), 'array_of_arrays' (array av [tidsstämpel, pris]-arrayer med avslutande null-punkt för stepline-diagram)." + "description": "Utdataformat för returnerad data. Alternativ: 'array_of_objects' (standard, array av objekt med anpassningsbara fältnamn), 'array_of_arrays' (array av [tidstämpel, pris]-arrays med avslutande null-punkt för stegdiagram)." }, "array_fields": { "name": "Array-fält", - "description": "Definiera vilka fält som ska inkluderas. Använd fältnamn inom måsvingar, separerade med kommatecken. Tillgängliga fält: start_time, price_per_kwh, level, rating_level, average. Fält aktiveras automatiskt även om include_*-alternativ inte är inställda. Lämna tomt för standard (endast tidsstämpel och pris)." + "description": "Definiera vilka fält som ska inkluderas. Använd fältnamn inom klammerparenteser, separerade med kommatecken. Tillgängliga fält: start_time, price_per_kwh, level, rating_level, average. Fält kommer automatiskt att aktiveras även om include_*-alternativ inte är inställda. Lämna tomt för standard (endast tidstämpel och pris)." }, "subunit_currency": { - "name": "Underenhet valuta", - "description": "Returnera priser i underenhet valutaenheter (öre för SEK/NOK, cent för EUR) istället för basvalutaenheter. Inaktiverad som standard." + "name": "Underenhetsvaluta", + "description": "Returnera priser i underenhetsvaluta (cent för EUR, øre för NOK/SEK) istället för basvalutaenheter. Inaktiverad som standard." }, "round_decimals": { "name": "Avrunda decimaler", - "description": "Antal decimaler att avrunda priser till (0-10). Om inte angivet används standardprecision (4 decimaler för basvaluta, 2 för underenhet valuta)." - }, - "include_level": { - "name": "Inkludera prisnivå", - "description": "Inkludera Tibber-prisnivåfältet (mycket billigt/billigt/normal/dyrt/mycket dyrt) i varje datapunkt." - }, - "include_rating_level": { - "name": "Inkludera prisvärdering", - "description": "Inkludera det beräknade prisvärderingsfältet (låg/normal/hög) baserat på dina konfigurerade trösklar i varje datapunkt." - }, - "include_average": { - "name": "Inkludera genomsnitt", - "description": "Inkludera dagligt genomsnittspris i varje datapunkt för jämförelse." - }, - "level_filter": { - "name": "Prisnivåfilter", - "description": "Filtrera intervaller för att endast inkludera specifika Tibber-prisnivåer (mycket billigt/billigt/normal/dyrt/mycket dyrt). Om inget anges inkluderas alla nivåer." - }, - "rating_level_filter": { - "name": "Prisvärderingsfilter", - "description": "Filtrera intervall för att endast inkludera specifika prisvärderingar (låg/normal/hög). Om inte angivet inkluderas alla värderingar." - }, - "period_filter": { - "name": "Periodfilter", - "description": "Filtrera intervall för att endast inkludera de inom Bästa pris- eller Topp pris-perioder. Alternativ: 'best_price' (endast intervall i Bästa pris-perioder), 'peak_price' (endast intervall i Topp pris-perioder). Om inte angivet inkluderas alla intervall. Använder förberäknad perioddata från binära sensorer." - }, - "insert_nulls": { - "name": "Infoga NULL-värden", - "description": "Kontrollera infogning av NULL-värden för filtrerad data. 'none' (standard): Inga NULL-värden, endast matchande intervall. 'segments': Lägg till NULL-punkter vid segmentgränser för rena luckor i diagram (rekommenderas för steglinjediagram). 'all': Infoga NULL för alla tidsstämplar där filtret inte matchar (användbart för kontinuerlig tidsserievisualisering)." - }, - "connect_segments": { - "name": "Anslut segment", - "description": "[ENDAST MED 'Infoga NULL-värden'] När aktiverad, läggs anslutningspunkter till vid segmentgränser för att visuellt ansluta olika prisnivå-segment i steglinjediagram. När priset går NER läggs en punkt med lägre pris till i slutet av nuvarande segment. När priset går UPP läggs en hållpunkt till före luckan. Detta skapar mjuka visuella övergångar mellan segment istället för abrupta luckor." - }, - "add_trailing_null": { - "name": "Lägg till avslutande null-punkt", - "description": "Lägg till en sista datapunkt med nullvärden (utom tidsstämpel) i slutet. Vissa diagrambibliotek behöver detta för att förhindra extrapolering/interpolering till visningsportens kant vid användning av trappstegsrendering. Lämna inaktiverad om inte ditt diagram kräver det." - }, - "start_time_field": { - "name": "Starttid-fältnamn", - "description": "Anpassat namn för starttid-fältet i utdata. Standard är 'start_time' om inte angivet." - }, - "end_time_field": { - "name": "Sluttid-fältnamn", - "description": "Anpassat namn för sluttid-fältet i utdata. Standard är 'end_time' om inte angivet. Används endast med period_filter." - }, - "price_field": { - "name": "Prisfältsnamn", - "description": "Anpassat namn för prisfältet i utdata. Standard är 'price_per_kwh'." - }, - "level_field": { - "name": "Prisnivåfältsnamn", - "description": "Anpassat namn för prisnivåfältet i utdata. Standard är 'level'. Används endast när include_level är aktiverad." - }, - "rating_level_field": { - "name": "Prisvärderingsfältsnamn", - "description": "Anpassat namn för prisvärderingsfältet i utdata. Standard är 'rating_level'. Används endast när include_rating_level är aktiverad." - }, - "average_field": { - "name": "Genomsnittsfältsnamn", - "description": "Anpassat namn för genomsnittsfältet i utdata. Standard är 'average'. Används endast när include_average är aktiverad." - }, - "metadata": { - "name": "Metadata", - "description": "Styr metadata-inkludering i svaret. 'include' (standard): Returnerar både diagramdata och metadata med prisstatistik, valutainformation, Y-axelförslag och tidsperiod. 'only': Returnerar endast metadata utan att bearbeta diagramdata (snabbt, användbart för dynamisk Y-axel konfiguration). 'none': Returnerar endast diagramdata utan metadata." + "description": "Antal decimaler att avrunda priser till (0-10). Om inget anges används standardprecision (4 decimaler för basvaluta, 2 för underenhetsvaluta)." }, "data_key": { "name": "Datanyckel", - "description": "Anpassat namn för datanyckeln på toppnivå i svaret. Standard är 'data' om inte angivet." + "description": "Anpassat namn för datanyckeln på toppnivå i svaret. Standard är 'data' om inget anges." + }, + "include_level": { + "name": "Inkludera nivå", + "description": "Inkludera Tibbers prisnivåfält (mycket billigt/billigt/normalt/dyrt/mycket dyrt) i varje datapunkt." + }, + "include_rating_level": { + "name": "Inkludera betygsnivå", + "description": "Inkludera det beräknade betygsnivåfältet (låg/normal/hög) baserat på dina konfigurerade tröskelvärden i varje datapunkt." + }, + "include_average": { + "name": "Inkludera medelvärde", + "description": "Inkludera dagligt medelpris i varje datapunkt för jämförelse." + }, + "level_filter": { + "name": "Nivåfilter", + "description": "Filtrera intervaller för att endast inkludera specifika Tibber-prisnivåer (mycket billigt/billigt/normalt/dyrt/mycket dyrt). Om inget anges inkluderas alla nivåer." + }, + "rating_level_filter": { + "name": "Betygsnivåfilter", + "description": "Filtrera intervaller för att endast inkludera specifika betygsnivåer (låg/normal/hög). Om inget anges inkluderas alla betygsnivåer." + }, + "period_filter": { + "name": "Periodfilter", + "description": "Filtrera intervaller för att endast inkludera de inom Bästa pris- eller Topppris-perioder. Alternativ: 'best_price' (endast intervaller i Bästa pris-perioder), 'peak_price' (endast intervaller i Topppris-perioder). Om inget anges inkluderas alla intervaller. Detta använder förberäknad perioddata från binära sensorer." + }, + "insert_nulls": { + "name": "Infoga NULL-värden", + "description": "Kontrollera infogning av NULL-värden för filtrerad data. 'none' (standard): Inga NULL-värden, endast matchande intervaller. 'segments': Lägg till NULL-punkter vid segmentgränser för rena luckor i diagram (rekommenderas för stegdiagram). 'all': Infoga NULL för alla tidstämplar där filtret inte matchar (användbart för kontinuerlig tidsserievisualisering)." + }, + "connect_segments": { + "name": "Anslut segment", + "description": "[ENDAST MED 'Infoga NULL-värden'] När aktiverad läggs anslutande punkter till vid segmentgränser för att visuellt ansluta olika prisnivåsegment i stegdiagram. När priset går NER vid en gräns läggs en punkt med det lägre priset till i slutet av det aktuella segmentet. När priset går UPP läggs en hållpunkt till innan luckan. Detta skapar smärre visuella övergångar mellan segment istället för abrupta luckor." + }, + "add_trailing_null": { + "name": "Lägg till avslutande null-punkt", + "description": "Lägg till en sista datapunkt med null-värden (förutom tidstämpel) i slutet. Vissa diagrambibliotek behöver detta för att förhindra extrapolering/interpolering till vyportens kant när steglinje-rendering används. Lämna inaktiverad om inte ditt diagram kräver det." + }, + "start_time_field": { + "name": "Starttidsfältnamn", + "description": "Anpassat namn för starttidsfältet i utdatan. Standard är 'start_time' om inget anges." + }, + "end_time_field": { + "name": "Sluttidsfältnamn", + "description": "Anpassat namn för sluttidsfältet i utdatan. Standard är 'end_time' om inget anges. Används endast med period_filter." + }, + "price_field": { + "name": "Prisfältnamn", + "description": "Anpassat namn för prisfältet i utdatan. Standard är 'price_per_kwh' om inget anges." + }, + "level_field": { + "name": "Nivåfältnamn", + "description": "Anpassat namn för nivåfältet i utdatan. Standard är 'level' om inget anges. Används endast när include_level är aktiverad." + }, + "rating_level_field": { + "name": "Betygsnivåfältnamn", + "description": "Anpassat namn för betygsnivåfältet i utdatan. Standard är 'rating_level' om inget anges. Används endast när include_rating_level är aktiverad." + }, + "average_field": { + "name": "Medelvärdesfältnamn", + "description": "Anpassat namn för medelvärdesfältet i utdatan. Standard är 'average' om inget anges. Används endast när include_average är aktiverad." + }, + "metadata": { + "name": "Metadata", + "description": "Kontrollera inkludering av metadata i svaret. 'include' (standard): Returnerar både diagramdata och metadata med prisstatistik, valutainfo, Y-axelförslag och tidsintervall. 'only': Returnerar endast metadata utan att bearbeta diagramdata (snabbt, användbart för dynamisk Y-axelkonfiguration). 'none': Returnerar endast diagramdata utan metadata." } } }, @@ -1037,8 +1046,8 @@ "description": "Tvingar en uppdatering av användardata (hem, profilinformation) från Tibber API. Detta kan vara användbart efter att ha gjort ändringar i ditt Tibber-konto eller vid felsökning av anslutningsproblem.", "fields": { "entry_id": { - "name": "Post-ID", - "description": "Konfigurationspost-ID för Tibber-integrationen." + "name": "Entry-ID", + "description": "Config entry-ID för Tibber-integrationen." } } } @@ -1046,7 +1055,7 @@ "selector": { "account_choice": { "options": { - "new_token": "Lägg till nytt Tibber-konto med API-token" + "new_token": "Lägg till ny Tibber-konto API-token" } }, "day": { @@ -1054,8 +1063,8 @@ "yesterday": "Igår", "today": "Idag", "tomorrow": "Imorgon", - "rolling_window": "Rullande fönster", - "rolling_window_autozoom": "Rullande fönster (Auto-Zoom)" + "rolling_window": "Glidande fönster", + "rolling_window_autozoom": "Glidande fönster (Auto-Zoom)" } }, "resolution": { @@ -1067,22 +1076,22 @@ "output_format": { "options": { "array_of_objects": "Array av objekt", - "array_of_arrays": "Array av arrayer" + "array_of_arrays": "Array av arrays" } }, "level_type": { "options": { - "rating_level": "Betygsättningsnivå (låg/normal/hög)", - "level": "Tibber-nivå (mycket billig till mycket dyr)" + "rating_level": "Betygnivå (låg/normal/hög)", + "level": "Tibber-nivå (mycket billigt till mycket dyrt)" } }, "level_filter": { "options": { - "very_cheap": "Mycket billig", - "cheap": "Billig", - "normal": "Normal", - "expensive": "Dyr", - "very_expensive": "Mycket dyr" + "very_cheap": "Mycket billigt", + "cheap": "Billigt", + "normal": "Normalt", + "expensive": "Dyrt", + "very_expensive": "Mycket dyrt" } }, "rating_level_filter": { @@ -1102,7 +1111,7 @@ "period_filter": { "options": { "best_price": "Bästa prisperioder", - "peak_price": "Topp prisperioder" + "peak_price": "Topprisperioder" } }, "metadata": { @@ -1133,7 +1142,7 @@ "currency_display_mode": { "options": { "base": "Basvaluta (€, kr)", - "subunit": "Underenhet valuta (ct, öre)" + "subunit": "Underenhetsvaluta (ct, øre)" } }, "average_sensor_display": {