2026-03-14 22:45:09 +08:00
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Globalization ;
using System.Linq ;
using System.Threading.Tasks ;
using Avalonia.Media ;
using CommunityToolkit.Mvvm.ComponentModel ;
using CommunityToolkit.Mvvm.Input ;
using LanMountainDesktop.Models ;
using LanMountainDesktop.Services ;
using LanMountainDesktop.Services.Settings ;
using LanMountainDesktop.Views.Components ;
namespace LanMountainDesktop.ViewModels ;
public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
{
private readonly ISettingsFacadeService _settingsFacade ;
private readonly LocalizationService _localizationService ;
private readonly ILocationService _locationService ;
private readonly WeatherLocationRefreshService _weatherLocationRefreshService ;
private string _languageCode ;
private bool _isInitializing ;
public WeatherSettingsPageViewModel (
ISettingsFacadeService settingsFacade ,
LocalizationService localizationService ,
ILocationService locationService ,
2026-03-22 02:53:31 +08:00
WeatherLocationRefreshService weatherLocationRefreshService ,
bool enableStartupPreviewRefresh = true )
2026-03-14 22:45:09 +08:00
{
_settingsFacade = settingsFacade ? ? throw new ArgumentNullException ( nameof ( settingsFacade ) ) ;
_localizationService = localizationService ? ? throw new ArgumentNullException ( nameof ( localizationService ) ) ;
_locationService = locationService ? ? throw new ArgumentNullException ( nameof ( locationService ) ) ;
_weatherLocationRefreshService = weatherLocationRefreshService ? ? throw new ArgumentNullException ( nameof ( weatherLocationRefreshService ) ) ;
var regionState = _settingsFacade . Region . Get ( ) ;
_languageCode = _localizationService . NormalizeLanguageCode ( regionState . LanguageCode ) ;
RefreshLocalizedText ( ) ;
LocationModes = CreateLocationModes ( ) ;
var weatherState = _settingsFacade . Weather . Get ( ) ;
_isInitializing = true ;
2026-03-15 17:08:07 +08:00
ApplySavedState ( weatherState , save : false ) ;
2026-03-14 22:45:09 +08:00
_isInitializing = false ;
IsLocationSupported = _locationService . IsSupported ;
UpdateModeVisibility ( ) ;
UpdateCurrentLocationSummary ( ) ;
LocationActionStatus = IsLocationSupported
? LocationReadyText
: LocationUnsupportedText ;
2026-03-22 02:53:31 +08:00
if ( enableStartupPreviewRefresh )
{
_ = RefreshPreviewAsync ( ) ;
}
2026-03-14 22:45:09 +08:00
}
public IReadOnlyList < SelectionOption > LocationModes { get ; }
public ObservableCollection < WeatherLocation > SearchResults { get ; } = [ ] ;
[ObservableProperty]
private string _pageTitle = string . Empty ;
[ObservableProperty]
private string _pageDescription = string . Empty ;
[ObservableProperty]
private string _previewHeader = string . Empty ;
[ObservableProperty]
private string _previewDescription = string . Empty ;
[ObservableProperty]
private string _locationSourceHeader = string . Empty ;
[ObservableProperty]
private string _locationSourceDescription = string . Empty ;
[ObservableProperty]
private string _citySearchHeader = string . Empty ;
[ObservableProperty]
private string _citySearchDescription = string . Empty ;
[ObservableProperty]
private string _coordinatesHeader = string . Empty ;
[ObservableProperty]
private string _coordinatesDescription = string . Empty ;
[ObservableProperty]
private string _locationServicesHeader = string . Empty ;
[ObservableProperty]
private string _locationServicesDescription = string . Empty ;
[ObservableProperty]
private string _alertFilterHeader = string . Empty ;
[ObservableProperty]
private string _alertFilterDescription = string . Empty ;
[ObservableProperty]
private string _requestHeader = string . Empty ;
[ObservableProperty]
private string _requestDescription = string . Empty ;
[ObservableProperty]
private string _searchPlaceholder = string . Empty ;
[ObservableProperty]
private string _searchButtonText = string . Empty ;
[ObservableProperty]
private string _applyCityButtonText = string . Empty ;
[ObservableProperty]
private string _refreshButtonText = string . Empty ;
[ObservableProperty]
private string _applyCoordinatesButtonText = string . Empty ;
[ObservableProperty]
private string _useCurrentLocationButtonText = string . Empty ;
[ObservableProperty]
private string _autoRefreshLabel = string . Empty ;
[ObservableProperty]
private string _latitudeLabel = string . Empty ;
[ObservableProperty]
private string _longitudeLabel = string . Empty ;
[ObservableProperty]
private string _locationKeyPlaceholder = string . Empty ;
[ObservableProperty]
private string _locationNamePlaceholder = string . Empty ;
[ObservableProperty]
private string _noTlsToggleText = string . Empty ;
[ObservableProperty]
private string _locationUnsupportedText = string . Empty ;
[ObservableProperty]
private string _locationReadyText = string . Empty ;
[ObservableProperty]
private string _locationRefreshingText = string . Empty ;
[ObservableProperty]
private string _footerHint = string . Empty ;
[ObservableProperty]
private SelectionOption _selectedLocationMode = new ( "CitySearch" , "City Search" ) ;
[ObservableProperty]
private bool _isCitySearchMode = true ;
[ObservableProperty]
private bool _isCoordinatesMode ;
[ObservableProperty]
private bool _isLocationSupported ;
[ObservableProperty]
private string _searchKeyword = string . Empty ;
[ObservableProperty]
private WeatherLocation ? _selectedSearchResult ;
[ObservableProperty]
private string _searchStatus = string . Empty ;
[ObservableProperty]
private bool _isSearching ;
[ObservableProperty]
private double _latitude ;
[ObservableProperty]
private double _longitude ;
[ObservableProperty]
private string _locationKey = string . Empty ;
[ObservableProperty]
private string _locationName = string . Empty ;
[ObservableProperty]
private bool _autoRefreshLocation ;
[ObservableProperty]
private string _excludedAlerts = string . Empty ;
[ObservableProperty]
private bool _noTlsRequests ;
[ObservableProperty]
private string _currentLocationSummary = string . Empty ;
[ObservableProperty]
private string _locationActionStatus = string . Empty ;
[ObservableProperty]
private bool _isRefreshingLocation ;
[ObservableProperty]
private bool _isRefreshingPreview ;
[ObservableProperty]
private IImage ? _previewIcon ;
[ObservableProperty]
private string _previewLocation = string . Empty ;
[ObservableProperty]
private string _previewTemperature = string . Empty ;
[ObservableProperty]
private string _previewCondition = string . Empty ;
[ObservableProperty]
private string _previewUpdated = string . Empty ;
[ObservableProperty]
private string _previewStatus = string . Empty ;
partial void OnSelectedLocationModeChanged ( SelectionOption value )
{
UpdateModeVisibility ( ) ;
UpdateCurrentLocationSummary ( ) ;
if ( _isInitializing | | value is null )
{
return ;
}
_settingsFacade . Weather . Save ( CreateEditableState ( value . Value ) ) ;
_ = RefreshPreviewAsync ( ) ;
}
partial void OnAutoRefreshLocationChanged ( bool value )
{
_ = value ;
if ( _isInitializing )
{
return ;
}
_settingsFacade . Weather . Save ( CreateEditableState ( ) ) ;
}
partial void OnExcludedAlertsChanged ( string value )
{
_ = value ;
if ( _isInitializing )
{
return ;
}
_settingsFacade . Weather . Save ( CreateEditableState ( ) ) ;
}
partial void OnNoTlsRequestsChanged ( bool value )
{
_ = value ;
if ( _isInitializing )
{
return ;
}
_settingsFacade . Weather . Save ( CreateEditableState ( ) ) ;
}
[RelayCommand]
private async Task SearchAsync ( )
{
SearchStatus = string . Empty ;
SearchResults . Clear ( ) ;
SelectedSearchResult = null ;
if ( string . IsNullOrWhiteSpace ( SearchKeyword ) )
{
SearchStatus = L ( "settings.weather.search_required" , "Please enter a city keyword first." ) ;
return ;
}
IsSearching = true ;
try
{
var result = await _settingsFacade . Weather . SearchLocationsAsync (
SearchKeyword . Trim ( ) ,
NormalizeWeatherLocale ( _languageCode ) ) ;
if ( ! result . Success )
{
SearchStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.search_failed_format" , "Search failed: {0}" ) ,
result . ErrorMessage ? ? result . ErrorCode ? ? L ( "settings.weather.preview_unknown" , "Unknown" ) ) ;
return ;
}
foreach ( var item in result . Data ? ? [ ] )
{
SearchResults . Add ( item ) ;
}
SearchStatus = SearchResults . Count = = 0
? L ( "settings.weather.search_no_results" , "No locations were found." )
: string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.search_result_count_format" , "Found {0} locations." ) ,
SearchResults . Count ) ;
SelectedSearchResult = SearchResults . FirstOrDefault ( ) ;
}
finally
{
IsSearching = false ;
}
}
[RelayCommand]
private async Task ApplyCitySelectionAsync ( )
{
if ( SelectedSearchResult is null )
{
SearchStatus = L ( "settings.weather.search_select_required" , "Please select one location from search results." ) ;
return ;
}
var selected = SelectedSearchResult ;
var nextState = new WeatherSettingsState (
"CitySearch" ,
selected . LocationKey ,
selected . Name ,
selected . Latitude ,
selected . Longitude ,
AutoRefreshLocation ,
ExcludedAlerts ? ? string . Empty ,
"HyperOS3" ,
NoTlsRequests ,
SearchKeyword ? . Trim ( ) ? ? string . Empty ) ;
ApplySavedState ( nextState ) ;
SearchStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.search_applied_format" , "Location applied: {0}" ) ,
selected . Name ) ;
await RefreshPreviewAsync ( ) ;
}
[RelayCommand]
private async Task ApplyCoordinatesAsync ( )
{
var nextState = CreateEditableState ( "Coordinates" ) ;
_settingsFacade . Weather . Save ( nextState ) ;
ApplySavedState ( nextState , save : false ) ;
SearchStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.coordinates_saved_format" , "Coordinates saved: {0:F4}, {1:F4}" ) ,
nextState . Latitude ,
nextState . Longitude ) ;
await RefreshPreviewAsync ( ) ;
}
[RelayCommand]
private async Task UseCurrentLocationAsync ( )
{
if ( ! IsLocationSupported )
{
LocationActionStatus = LocationUnsupportedText ;
return ;
}
IsRefreshingLocation = true ;
LocationActionStatus = LocationRefreshingText ;
try
{
var result = await _weatherLocationRefreshService . RefreshCurrentLocationAsync ( ) ;
if ( ! result . Success | | result . AppliedState is null )
{
LocationActionStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.location_refresh_failed_format" , "Failed to get current location: {0}" ) ,
result . ErrorMessage ? ? result . LocationResult ? . FailureReason . ToString ( ) ? ? L ( "settings.weather.preview_unknown" , "Unknown" ) ) ;
return ;
}
ApplySavedState ( result . AppliedState , save : false ) ;
LocationActionStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.location_refresh_success_format" , "Current location applied: {0}" ) ,
result . AppliedState . LocationName ) ;
await RefreshPreviewAsync ( ) ;
}
finally
{
IsRefreshingLocation = false ;
}
}
[RelayCommand]
private async Task RefreshPreviewAsync ( )
{
IsRefreshingPreview = true ;
try
{
var state = ResolvePreviewState ( ) ;
if ( string . IsNullOrWhiteSpace ( state . LocationKey ) )
{
PreviewStatus = L ( "settings.weather.preview_missing_location" , "Please apply one weather location before testing." ) ;
PreviewIcon = null ;
PreviewLocation = CurrentLocationSummary ;
PreviewTemperature = "--" ;
PreviewCondition = string . Empty ;
PreviewUpdated = string . Empty ;
return ;
}
var result = await _settingsFacade . Weather . GetWeatherInfoService ( ) . GetWeatherAsync (
new WeatherQuery (
state . LocationKey ,
state . Latitude ,
state . Longitude ,
ForecastDays : 3 ,
Locale : NormalizeWeatherLocale ( _languageCode ) ,
ForceRefresh : true ) ) ;
if ( ! result . Success | | result . Data is null )
{
PreviewStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.preview_failed_format" , "Test fetch failed: {0}" ) ,
result . ErrorMessage ? ? result . ErrorCode ? ? L ( "settings.weather.preview_unknown" , "Unknown" ) ) ;
PreviewIcon = null ;
return ;
}
var snapshot = result . Data ;
var isNight = snapshot . Current . IsDaylight . HasValue
? ! snapshot . Current . IsDaylight . Value
: _settingsFacade . Theme . Get ( ) . IsNightMode ;
2026-03-15 17:08:07 +08:00
var preview = XiaomiWeatherVisualResolver . Resolve (
snapshot . Current . WeatherText ,
snapshot . Current . WeatherCode ,
isNight ,
_languageCode ) ;
PreviewIcon = HyperOS3WeatherAssetLoader . LoadImage ( preview . PrimaryIconAsset ) ;
2026-03-14 22:45:09 +08:00
PreviewLocation = string . IsNullOrWhiteSpace ( snapshot . LocationName )
? state . LocationName
: snapshot . LocationName ! ;
PreviewTemperature = snapshot . Current . TemperatureC . HasValue
? string . Format ( CultureInfo . InvariantCulture , "{0:0.#}°C" , snapshot . Current . TemperatureC . Value )
: "--" ;
2026-03-15 17:08:07 +08:00
PreviewCondition = preview . DisplayText ;
2026-03-14 22:45:09 +08:00
var updatedAt = ( snapshot . ObservationTime ? ? snapshot . FetchedAt ) . ToLocalTime ( ) ;
PreviewUpdated = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.preview_updated_format" , "Updated {0}" ) ,
updatedAt . ToString ( "g" , ResolveCulture ( ) ) ) ;
PreviewStatus = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.preview_success_format" , "Test success: {0} · {1} · {2}" ) ,
PreviewLocation ,
PreviewTemperature ,
PreviewCondition ) ;
}
finally
{
IsRefreshingPreview = false ;
}
}
2026-03-22 02:53:31 +08:00
internal void ApplyDesignTimePreview ( )
{
_isInitializing = true ;
var previewLocation = new WeatherLocation (
"Shenzhen Nanshan" ,
"101280601" ,
22.5431 ,
114.0579 ,
"Guangdong, China" ) ;
var alternateLocation = new WeatherLocation (
"Shanghai Pudong" ,
"101020600" ,
31.2304 ,
121.4737 ,
"Shanghai, China" ) ;
SelectedLocationMode = LocationModes . FirstOrDefault ( option = >
string . Equals ( option . Value , "CitySearch" , StringComparison . OrdinalIgnoreCase ) )
? ? LocationModes [ 0 ] ;
SearchKeyword = "shenzhen" ;
SelectedSearchResult = previewLocation ;
SearchResults . Clear ( ) ;
SearchResults . Add ( previewLocation ) ;
SearchResults . Add ( alternateLocation ) ;
Latitude = previewLocation . Latitude ;
Longitude = previewLocation . Longitude ;
LocationKey = previewLocation . LocationKey ;
LocationName = previewLocation . Name ;
AutoRefreshLocation = true ;
ExcludedAlerts = "Heat\nThunderstorm" ;
NoTlsRequests = false ;
IsLocationSupported = true ;
IsRefreshingLocation = false ;
IsRefreshingPreview = false ;
_isInitializing = false ;
UpdateModeVisibility ( ) ;
UpdateCurrentLocationSummary ( ) ;
var preview = XiaomiWeatherVisualResolver . Resolve (
"Partly cloudy" ,
4 ,
isNight : false ,
_languageCode ) ;
SearchStatus = "2 sample locations are shown for design preview." ;
LocationActionStatus = "Using mocked Windows location support in design mode." ;
PreviewIcon = HyperOS3WeatherAssetLoader . LoadImage ( preview . PrimaryIconAsset ) ;
PreviewLocation = previewLocation . Name ;
PreviewTemperature = "24 deg C" ;
PreviewCondition = preview . DisplayText ;
PreviewUpdated = "Updated 09:42" ;
PreviewStatus = "Preview data is mocked for Avalonia design mode." ;
}
2026-03-14 22:45:09 +08:00
private void RefreshLocalizedText ( )
{
PageTitle = L ( "settings.weather.title" , "Weather" ) ;
PageDescription = L ( "settings.weather.description" , "Configure weather location, automatic positioning, and Xiaomi weather preview." ) ;
PreviewHeader = L ( "settings.weather.preview_panel_header" , "Weather Preview" ) ;
PreviewDescription = L ( "settings.weather.preview_panel_desc" , "Refresh and verify current weather service status." ) ;
LocationSourceHeader = L ( "settings.weather.location_source_header" , "Location Source" ) ;
LocationSourceDescription = L ( "settings.weather.location_source_desc" , "Choose how weather widgets resolve location." ) ;
CitySearchHeader = L ( "settings.weather.city_search_header" , "City Search" ) ;
CitySearchDescription = L ( "settings.weather.city_search_desc" , "Search cities and apply one weather location." ) ;
CoordinatesHeader = L ( "settings.weather.coordinates_header" , "Coordinates" ) ;
CoordinatesDescription = L ( "settings.weather.coordinates_desc" , "Set latitude/longitude and optional key/name." ) ;
LocationServicesHeader = L ( "settings.weather.location_services_header" , "Location Service" ) ;
LocationServicesDescription = L ( "settings.weather.location_services_desc" , "Use the current Windows location and decide whether it refreshes automatically at startup." ) ;
AlertFilterHeader = L ( "settings.weather.alert_filter_header" , "Excluded Alerts" ) ;
AlertFilterDescription = L ( "settings.weather.alert_filter_desc" , "Alerts containing these words will not be shown. One rule per line." ) ;
RequestHeader = L ( "settings.weather.no_tls_header" , "No TLS Weather Request" ) ;
RequestDescription = L ( "settings.weather.no_tls_desc" , "Not recommended. Enable only for incompatible network environments." ) ;
SearchPlaceholder = L ( "settings.weather.search_placeholder" , "e.g. Beijing" ) ;
SearchButtonText = L ( "settings.weather.search_button" , "Search" ) ;
ApplyCityButtonText = L ( "settings.weather.apply_city_button" , "Apply City" ) ;
RefreshButtonText = L ( "settings.weather.refresh_button" , "Refresh" ) ;
ApplyCoordinatesButtonText = L ( "settings.weather.apply_coordinates_button" , "Apply Coordinates" ) ;
UseCurrentLocationButtonText = L ( "settings.weather.use_current_location" , "Use Current Location" ) ;
AutoRefreshLabel = L ( "settings.weather.auto_refresh" , "Auto refresh location on startup" ) ;
LatitudeLabel = L ( "settings.weather.latitude_label" , "Latitude" ) ;
LongitudeLabel = L ( "settings.weather.longitude_label" , "Longitude" ) ;
LocationKeyPlaceholder = L ( "settings.weather.location_key_placeholder" , "Location key (optional)" ) ;
LocationNamePlaceholder = L ( "settings.weather.location_name_placeholder" , "Display name (optional)" ) ;
NoTlsToggleText = L ( "settings.weather.no_tls_toggle" , "Allow non-TLS request fallback" ) ;
LocationUnsupportedText = L ( "settings.weather.location_unsupported" , "Current platform does not support retrieving the current location." ) ;
LocationReadyText = L ( "settings.weather.location_ready" , "You can use the current Windows location." ) ;
LocationRefreshingText = L ( "settings.weather.location_refreshing" , "Requesting current location…" ) ;
FooterHint = L ( "settings.weather.footer_hint" , "Desktop weather widgets will reuse the location and alert exclusion settings configured here." ) ;
}
private IReadOnlyList < SelectionOption > CreateLocationModes ( )
{
return
[
new SelectionOption ( "CitySearch" , L ( "settings.weather.mode_city_search" , "City Search" ) ) ,
new SelectionOption ( "Coordinates" , L ( "settings.weather.mode_coordinates" , "Coordinates" ) )
] ;
}
private void UpdateModeVisibility ( )
{
var mode = SelectedLocationMode ? . Value ? ? "CitySearch" ;
IsCitySearchMode = string . Equals ( mode , "CitySearch" , StringComparison . OrdinalIgnoreCase ) ;
IsCoordinatesMode = string . Equals ( mode , "Coordinates" , StringComparison . OrdinalIgnoreCase ) ;
}
private void UpdateCurrentLocationSummary ( )
{
var state = CreateEditableState ( ) ;
var modeLabel = SelectedLocationMode ? . Label ? ? state . LocationMode ;
if ( string . Equals ( state . LocationMode , "CitySearch" , StringComparison . OrdinalIgnoreCase ) )
{
CurrentLocationSummary = string . IsNullOrWhiteSpace ( state . LocationKey )
? L ( "settings.weather.status_city_empty" , "No city location is configured." )
: string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.status_city_format" , "Mode: {0} | {1} | Key: {2}" ) ,
modeLabel ,
string . IsNullOrWhiteSpace ( state . LocationName ) ? L ( "settings.weather.location_not_selected" , "No location selected" ) : state . LocationName ,
state . LocationKey ) ;
return ;
}
CurrentLocationSummary = string . Format (
ResolveCulture ( ) ,
L ( "settings.weather.status_coordinates_format" , "Mode: {0} | Lat {1:F4}, Lon {2:F4} | Key: {3}" ) ,
modeLabel ,
state . Latitude ,
state . Longitude ,
state . LocationKey ) ;
}
private WeatherSettingsState CreateEditableState ( string? locationMode = null )
{
var mode = locationMode ? ? SelectedLocationMode ? . Value ? ? "CitySearch" ;
var locationKey = LocationKey ? . Trim ( ) ? ? string . Empty ;
var locationName = LocationName ? . Trim ( ) ? ? string . Empty ;
if ( string . Equals ( mode , "Coordinates" , StringComparison . OrdinalIgnoreCase ) )
{
if ( string . IsNullOrWhiteSpace ( locationKey ) )
{
locationKey = BuildCoordinateKey ( Latitude , Longitude ) ;
}
if ( string . IsNullOrWhiteSpace ( locationName ) )
{
locationName = BuildCoordinateDisplayName ( Latitude , Longitude ) ;
}
}
return new WeatherSettingsState (
mode ,
locationKey ,
locationName ,
Latitude ,
Longitude ,
AutoRefreshLocation ,
ExcludedAlerts ? ? string . Empty ,
"HyperOS3" ,
NoTlsRequests ,
SearchKeyword ? . Trim ( ) ? ? string . Empty ) ;
}
private WeatherSettingsState ResolvePreviewState ( )
{
if ( IsCitySearchMode & & SelectedSearchResult is not null )
{
return new WeatherSettingsState (
"CitySearch" ,
SelectedSearchResult . LocationKey ,
SelectedSearchResult . Name ,
SelectedSearchResult . Latitude ,
SelectedSearchResult . Longitude ,
AutoRefreshLocation ,
ExcludedAlerts ? ? string . Empty ,
"HyperOS3" ,
NoTlsRequests ,
SearchKeyword ? . Trim ( ) ? ? string . Empty ) ;
}
return CreateEditableState ( ) ;
}
private void ApplySavedState ( WeatherSettingsState state , bool save = true )
{
if ( save )
{
_settingsFacade . Weather . Save ( state ) ;
}
_isInitializing = true ;
SelectedLocationMode = LocationModes . FirstOrDefault ( option = >
string . Equals ( option . Value , state . LocationMode , StringComparison . OrdinalIgnoreCase ) )
? ? LocationModes [ 0 ] ;
Latitude = state . Latitude ;
Longitude = state . Longitude ;
LocationKey = state . LocationKey ;
LocationName = state . LocationName ;
AutoRefreshLocation = state . AutoRefreshLocation ;
ExcludedAlerts = state . ExcludedAlerts ;
NoTlsRequests = state . NoTlsRequests ;
SearchKeyword = state . LocationQuery ;
_isInitializing = false ;
UpdateModeVisibility ( ) ;
UpdateCurrentLocationSummary ( ) ;
}
private string BuildCoordinateDisplayName ( double latitude , double longitude )
{
return string . Format (
CultureInfo . InvariantCulture ,
L ( "settings.weather.coordinates_default_name_format" , "Coordinate {0:F4}, {1:F4}" ) ,
latitude ,
longitude ) ;
}
private static string BuildCoordinateKey ( double latitude , double longitude )
{
return FormattableString . Invariant ( $"coord:{latitude:F4},{longitude:F4}" ) ;
}
private string L ( string key , string fallback )
{
return _localizationService . GetString ( _languageCode , key , fallback ) ;
}
private CultureInfo ResolveCulture ( )
{
try
{
return CultureInfo . GetCultureInfo ( _languageCode ) ;
}
catch ( CultureNotFoundException )
{
return CultureInfo . InvariantCulture ;
}
}
private static string NormalizeWeatherLocale ( string? languageCode )
{
return string . Equals ( languageCode , "en-US" , StringComparison . OrdinalIgnoreCase )
? "en_us"
: "zh_cn" ;
}
}