new design with dynamic pricing
This commit is contained in:
parent
0169576620
commit
e0d6d04409
|
|
@ -21,7 +21,13 @@ public class Configuration
|
||||||
public double? TimeChargeandDischargePower { get; set; }
|
public double? TimeChargeandDischargePower { get; set; }
|
||||||
public DateTime? StartTimeChargeandDischargeDayandTime { get; set; }
|
public DateTime? StartTimeChargeandDischargeDayandTime { get; set; }
|
||||||
public DateTime? StopTimeChargeandDischargeDayandTime { get; set; }
|
public DateTime? StopTimeChargeandDischargeDayandTime { get; set; }
|
||||||
|
|
||||||
|
// Sinexcel Dynamic Pricing (under GridPriority) — strings for demo; engine will parse later.
|
||||||
|
public string? DynamicPricingMode { get; set; }
|
||||||
|
public string? CurrentPrice { get; set; }
|
||||||
|
public string? PriceToSell { get; set; }
|
||||||
|
public string? PriceToBuy { get; set; }
|
||||||
|
|
||||||
public String GetConfigurationString()
|
public String GetConfigurationString()
|
||||||
{
|
{
|
||||||
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
||||||
|
|
@ -48,7 +54,8 @@ public class Configuration
|
||||||
{
|
{
|
||||||
return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
||||||
$"InverterNumber: {InverterNumber}, BatteriesCount: {BatteriesCount}, BatteriesCountPerInverter: [{(BatteriesCountPerInverter != null ? string.Join(", ", BatteriesCountPerInverter) : "")}], ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, ControlPermission:{ControlPermission}, "+
|
$"InverterNumber: {InverterNumber}, BatteriesCount: {BatteriesCount}, BatteriesCountPerInverter: [{(BatteriesCountPerInverter != null ? string.Join(", ", BatteriesCountPerInverter) : "")}], ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, ControlPermission:{ControlPermission}, "+
|
||||||
$"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}";
|
$"SinexcelTimeChargeandDischargePower: {TimeChargeandDischargePower}, SinexcelStartTimeChargeandDischargeDayandTime: {StartTimeChargeandDischargeDayandTime}, SinexcelStopTimeChargeandDischargeDayandTime: {StopTimeChargeandDischargeDayandTime}, " +
|
||||||
|
$"DynamicPricingMode: {DynamicPricingMode}, CurrentPrice: {CurrentPrice}, PriceToSell: {PriceToSell}, PriceToBuy: {PriceToBuy}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: SodistoreGrid — update configuration fields when defined
|
// TODO: SodistoreGrid — update configuration fields when defined
|
||||||
|
|
|
||||||
|
|
@ -709,6 +709,12 @@ export type ConfigurationValues = {
|
||||||
timeChargeandDischargePower?: number;
|
timeChargeandDischargePower?: number;
|
||||||
startTimeChargeandDischargeDayandTime?: Date | null;
|
startTimeChargeandDischargeDayandTime?: Date | null;
|
||||||
stopTimeChargeandDischargeDayandTime?: Date | null;
|
stopTimeChargeandDischargeDayandTime?: Date | null;
|
||||||
|
|
||||||
|
// For sodistoreHome-Sinexcel: Dynamic Pricing (under GridPriority)
|
||||||
|
dynamicPricingMode?: string;
|
||||||
|
currentPrice?: string;
|
||||||
|
priceToSell?: string;
|
||||||
|
priceToBuy?: string;
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
// export interface Pv {
|
// export interface Pv {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
|
@ -26,7 +27,12 @@ import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import { INSTALLATION_PRESETS } from '../Information/installationSetupUtils';
|
import {
|
||||||
|
INSTALLATION_PRESETS,
|
||||||
|
buildSodistoreProPreset,
|
||||||
|
getPresetsForDevice,
|
||||||
|
PresetConfig
|
||||||
|
} from '../Information/installationSetupUtils';
|
||||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||||
import {DateTimePicker } from '@mui/x-date-pickers';
|
import {DateTimePicker } from '@mui/x-date-pickers';
|
||||||
|
|
@ -60,6 +66,14 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
'PvPriorityCharging': 'GridPriority',
|
'PvPriorityCharging': 'GridPriority',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Dynamic Pricing Mode — backend enum values with UI labels
|
||||||
|
const DynamicPricingOptions = ['Disabled', 'SpotPrice', 'Tou'] as const;
|
||||||
|
const dynamicPricingLabelKey: Record<string, string> = {
|
||||||
|
Disabled: 'dynamicPricingOff',
|
||||||
|
SpotPrice: 'dynamicPricingOn',
|
||||||
|
Tou: 'dynamicPricingTou',
|
||||||
|
};
|
||||||
|
|
||||||
const [errors, setErrors] = useState({
|
const [errors, setErrors] = useState({
|
||||||
minimumSoC: false,
|
minimumSoC: false,
|
||||||
gridSetPoint: false
|
gridSetPoint: false
|
||||||
|
|
@ -122,6 +136,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
return parsed && parsed.year() >= 2020 ? parsed.toDate() : new Date();
|
return parsed && parsed.year() >= 2020 ? parsed.toDate() : new Date();
|
||||||
})(),
|
})(),
|
||||||
controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true",
|
controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true",
|
||||||
|
dynamicPricingMode: (props.values.Config as any).DynamicPricingMode ?? 'Disabled',
|
||||||
|
currentPrice: (props.values.Config as any).CurrentPrice?.toString() ?? '',
|
||||||
|
priceToSell: (props.values.Config as any).PriceToSell?.toString() ?? '',
|
||||||
|
priceToBuy: (props.values.Config as any).PriceToBuy?.toString() ?? '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -263,7 +281,11 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
stopTimeChargeandDischargeDayandTime: formValues.stopTimeChargeandDischargeDayandTime
|
stopTimeChargeandDischargeDayandTime: formValues.stopTimeChargeandDischargeDayandTime
|
||||||
? new Date(formValues.stopTimeChargeandDischargeDayandTime.getTime() - formValues.stopTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000)
|
? new Date(formValues.stopTimeChargeandDischargeDayandTime.getTime() - formValues.stopTimeChargeandDischargeDayandTime.getTimezoneOffset() * 60000)
|
||||||
: null,
|
: null,
|
||||||
controlPermission:formValues.controlPermission
|
controlPermission:formValues.controlPermission,
|
||||||
|
dynamicPricingMode: formValues.dynamicPricingMode,
|
||||||
|
currentPrice: formValues.currentPrice,
|
||||||
|
priceToSell: formValues.priceToSell,
|
||||||
|
priceToBuy: formValues.priceToBuy,
|
||||||
};
|
};
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -441,12 +463,15 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{device === 4 && (() => {
|
{device === 4 && (() => {
|
||||||
const preset = INSTALLATION_PRESETS[props.installation.installationModel];
|
// Mirror Information tab's preset derivation so both views always agree.
|
||||||
const inverterCount = preset ? preset.length : 1;
|
const isSodistorePro = product === 5;
|
||||||
const pvStrings = (props.installation.pvStringsPerInverter || '')
|
const model = props.installation.installationModel;
|
||||||
.split(',')
|
const presetConfig: PresetConfig | null = isSodistorePro
|
||||||
.map((s) => s.trim())
|
? (model && parseInt(model, 10) > 0
|
||||||
.filter((s) => s !== '');
|
? buildSodistoreProPreset(parseInt(model, 10))
|
||||||
|
: null)
|
||||||
|
: (getPresetsForDevice(device)[model] || null);
|
||||||
|
const inverterCount = presetConfig?.length ?? 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -459,8 +484,10 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{preset && preset.map((clusters, i) => {
|
{Array.from({ length: inverterCount }, (_, i) => {
|
||||||
const batteriesInInverter = clusters.reduce((a, b) => a + b, 0);
|
const batteriesInInverter = presetConfig
|
||||||
|
? (presetConfig[i] ?? []).reduce((a, b) => a + b, 0)
|
||||||
|
: 0;
|
||||||
return (
|
return (
|
||||||
<div key={`battCount_${i}`} style={{ marginBottom: '5px' }}>
|
<div key={`battCount_${i}`} style={{ marginBottom: '5px' }}>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -475,20 +502,6 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{Array.from({ length: inverterCount }, (_, i) => (
|
|
||||||
<div key={`pvCount_${i}`} style={{ marginBottom: '5px' }}>
|
|
||||||
<TextField
|
|
||||||
label={intl.formatMessage(
|
|
||||||
{ id: 'pvInInverter' },
|
|
||||||
{ number: i + 1 }
|
|
||||||
)}
|
|
||||||
value={pvStrings[i] ?? 0}
|
|
||||||
InputProps={{ readOnly: true }}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
@ -679,6 +692,114 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* --- Sinexcel + GridPriority: Dynamic Pricing --- */}
|
||||||
|
{device === 4 &&
|
||||||
|
OperatingPriorityOptions[formValues.operatingPriority] === 'GridPriority' && (
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" sx={{ mt: 3, mb: 1 }}>
|
||||||
|
<FormattedMessage id="dynamicPricing" defaultMessage="Dynamic Pricing" />
|
||||||
|
</Typography>
|
||||||
|
<Divider sx={{ mb: 2 }} />
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||||
|
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
||||||
|
<FormattedMessage id="dynamicPricingMode" defaultMessage="Dynamic Pricing Mode" />
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formValues.dynamicPricingMode ?? 'Disabled'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFormDirty(true);
|
||||||
|
setFormValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
dynamicPricingMode: e.target.value as string,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{DynamicPricingOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt} value={opt}>
|
||||||
|
{intl.formatMessage({ id: dynamicPricingLabelKey[opt] })}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formValues.dynamicPricingMode === 'SpotPrice' && (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({ id: 'networkProvider' })}
|
||||||
|
value={
|
||||||
|
props.installation.networkProvider ||
|
||||||
|
intl.formatMessage({ id: 'networkProviderSetOnInformationTab' })
|
||||||
|
}
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({ id: 'currentPrice' })}
|
||||||
|
name="currentPrice"
|
||||||
|
value={formValues.currentPrice ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||||
|
setFormDirty(true);
|
||||||
|
setFormValues((prev) => ({ ...prev, currentPrice: v }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({ id: 'priceToSell' })}
|
||||||
|
name="priceToSell"
|
||||||
|
value={formValues.priceToSell ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||||
|
setFormDirty(true);
|
||||||
|
setFormValues((prev) => ({ ...prev, priceToSell: v }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({ id: 'priceToBuy' })}
|
||||||
|
name="priceToBuy"
|
||||||
|
value={formValues.priceToBuy ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
if (v === '' || /^\d*\.?\d*$/.test(v)) {
|
||||||
|
setFormDirty(true);
|
||||||
|
setFormValues((prev) => ({ ...prev, priceToBuy: v }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: <InputAdornment position="end">CHF/kWh</InputAdornment>,
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,15 @@
|
||||||
"systemSettings": "Systemeinstellungen",
|
"systemSettings": "Systemeinstellungen",
|
||||||
"pvPerInverter": "PV pro Wechselrichter",
|
"pvPerInverter": "PV pro Wechselrichter",
|
||||||
"pvInInverter": "PV in Wechselrichter {number}",
|
"pvInInverter": "PV in Wechselrichter {number}",
|
||||||
|
"dynamicPricing": "Dynamische Preisgestaltung",
|
||||||
|
"dynamicPricingMode": "Modus der dynamischen Preisgestaltung",
|
||||||
|
"dynamicPricingOff": "Aus",
|
||||||
|
"dynamicPricingOn": "Ein",
|
||||||
|
"dynamicPricingTou": "TOU",
|
||||||
|
"currentPrice": "Aktueller Preis",
|
||||||
|
"priceToSell": "Verkaufspreis",
|
||||||
|
"priceToBuy": "Kaufpreis",
|
||||||
|
"networkProviderSetOnInformationTab": "Im Informations-Tab festlegen",
|
||||||
"tourLanguageTitle": "Sprache",
|
"tourLanguageTitle": "Sprache",
|
||||||
"tourLanguageContent": "Wählen Sie Ihre bevorzugte Sprache. Die Oberfläche unterstützt Englisch, Deutsch, Französisch und Italienisch.",
|
"tourLanguageContent": "Wählen Sie Ihre bevorzugte Sprache. Die Oberfläche unterstützt Englisch, Deutsch, Französisch und Italienisch.",
|
||||||
"tourExploreTitle": "Installation erkunden",
|
"tourExploreTitle": "Installation erkunden",
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,15 @@
|
||||||
"systemSettings": "System Settings",
|
"systemSettings": "System Settings",
|
||||||
"pvPerInverter": "PV per Inverter",
|
"pvPerInverter": "PV per Inverter",
|
||||||
"pvInInverter": "PV in Inverter {number}",
|
"pvInInverter": "PV in Inverter {number}",
|
||||||
|
"dynamicPricing": "Dynamic Pricing",
|
||||||
|
"dynamicPricingMode": "Dynamic Pricing Mode",
|
||||||
|
"dynamicPricingOff": "Off",
|
||||||
|
"dynamicPricingOn": "On",
|
||||||
|
"dynamicPricingTou": "TOU",
|
||||||
|
"currentPrice": "Current Price",
|
||||||
|
"priceToSell": "Price to Sell",
|
||||||
|
"priceToBuy": "Price to Buy",
|
||||||
|
"networkProviderSetOnInformationTab": "Set on Information tab",
|
||||||
"tourLanguageTitle": "Language",
|
"tourLanguageTitle": "Language",
|
||||||
"tourLanguageContent": "Choose your preferred language. The interface supports English, German, French, and Italian.",
|
"tourLanguageContent": "Choose your preferred language. The interface supports English, German, French, and Italian.",
|
||||||
"tourExploreTitle": "Explore an Installation",
|
"tourExploreTitle": "Explore an Installation",
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,15 @@
|
||||||
"systemSettings": "Paramètres système",
|
"systemSettings": "Paramètres système",
|
||||||
"pvPerInverter": "PV par onduleur",
|
"pvPerInverter": "PV par onduleur",
|
||||||
"pvInInverter": "PV dans l'onduleur {number}",
|
"pvInInverter": "PV dans l'onduleur {number}",
|
||||||
|
"dynamicPricing": "Tarification dynamique",
|
||||||
|
"dynamicPricingMode": "Mode de tarification dynamique",
|
||||||
|
"dynamicPricingOff": "Désactivé",
|
||||||
|
"dynamicPricingOn": "Activé",
|
||||||
|
"dynamicPricingTou": "TOU",
|
||||||
|
"currentPrice": "Prix actuel",
|
||||||
|
"priceToSell": "Prix de vente",
|
||||||
|
"priceToBuy": "Prix d'achat",
|
||||||
|
"networkProviderSetOnInformationTab": "À définir dans l'onglet Informations",
|
||||||
"tourLanguageTitle": "Langue",
|
"tourLanguageTitle": "Langue",
|
||||||
"tourLanguageContent": "Choisissez votre langue préférée. L'interface est disponible en anglais, allemand, français et italien.",
|
"tourLanguageContent": "Choisissez votre langue préférée. L'interface est disponible en anglais, allemand, français et italien.",
|
||||||
"tourExploreTitle": "Explorer une installation",
|
"tourExploreTitle": "Explorer une installation",
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,15 @@
|
||||||
"systemSettings": "Impostazioni di sistema",
|
"systemSettings": "Impostazioni di sistema",
|
||||||
"pvPerInverter": "PV per inverter",
|
"pvPerInverter": "PV per inverter",
|
||||||
"pvInInverter": "PV nell'inverter {number}",
|
"pvInInverter": "PV nell'inverter {number}",
|
||||||
|
"dynamicPricing": "Prezzi dinamici",
|
||||||
|
"dynamicPricingMode": "Modalità prezzi dinamici",
|
||||||
|
"dynamicPricingOff": "Off",
|
||||||
|
"dynamicPricingOn": "On",
|
||||||
|
"dynamicPricingTou": "TOU",
|
||||||
|
"currentPrice": "Prezzo attuale",
|
||||||
|
"priceToSell": "Prezzo di vendita",
|
||||||
|
"priceToBuy": "Prezzo di acquisto",
|
||||||
|
"networkProviderSetOnInformationTab": "Imposta nella scheda Informazioni",
|
||||||
"tourLanguageTitle": "Lingua",
|
"tourLanguageTitle": "Lingua",
|
||||||
"tourLanguageContent": "Scegli la tua lingua preferita. L'interfaccia supporta inglese, tedesco, francese e italiano.",
|
"tourLanguageContent": "Scegli la tua lingua preferita. L'interfaccia supporta inglese, tedesco, francese e italiano.",
|
||||||
"tourExploreTitle": "Esplora un'installazione",
|
"tourExploreTitle": "Esplora un'installazione",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue