Skip to content

Commit 5b2ec66

Browse files
author
MPCoreDeveloper
committed
viewer added to project ( work in progress!! )
1 parent ad232b6 commit 5b2ec66

19 files changed

+820
-51
lines changed

SharpCoreDB.Viewer/App.axaml.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
using Avalonia.Controls.ApplicationLifetimes;
33
using Avalonia.Data.Core;
44
using Avalonia.Data.Core.Plugins;
5-
using System.Linq;
65
using Avalonia.Markup.Xaml;
6+
using Avalonia.Styling;
7+
using SharpCoreDB.Viewer.Services;
78
using SharpCoreDB.Viewer.ViewModels;
89
using SharpCoreDB.Viewer.Views;
910

@@ -21,8 +22,19 @@ public override void OnFrameworkInitializationCompleted()
2122
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
2223
{
2324
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
24-
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
2525
DisableAvaloniaDataAnnotationValidation();
26+
27+
// Load and apply settings
28+
var settingsService = SettingsService.Instance;
29+
settingsService.ApplySettings();
30+
ApplyTheme(settingsService.Settings.Theme);
31+
32+
// Subscribe to settings changes
33+
settingsService.SettingsChanged += (s, settings) =>
34+
{
35+
ApplyTheme(settings.Theme);
36+
};
37+
2638
desktop.MainWindow = new MainWindow
2739
{
2840
DataContext = new MainWindowViewModel(),
@@ -32,6 +44,16 @@ public override void OnFrameworkInitializationCompleted()
3244
base.OnFrameworkInitializationCompleted();
3345
}
3446

47+
private void ApplyTheme(string themeName)
48+
{
49+
RequestedThemeVariant = themeName switch
50+
{
51+
"Dark" => ThemeVariant.Dark,
52+
"Light" => ThemeVariant.Light,
53+
_ => ThemeVariant.Default
54+
};
55+
}
56+
3557
private void DisableAvaloniaDataAnnotationValidation()
3658
{
3759
// Get an array of plugins to remove
-172 KB
Binary file not shown.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Avalonia.Data.Converters;
2+
using Avalonia.Markup.Xaml;
3+
using Avalonia.Data;
4+
using SharpCoreDB.Viewer.Services;
5+
using SharpCoreDB.Viewer.Helpers;
6+
using System.Globalization;
7+
8+
namespace SharpCoreDB.Viewer.Converters;
9+
10+
/// <summary>
11+
/// Markup extension for localized strings in XAML that supports dynamic language switching
12+
/// Usage: Text="{loc:Localize Connect}"
13+
/// </summary>
14+
public class LocalizeExtension : MarkupExtension
15+
{
16+
public string Key { get; set; } = string.Empty;
17+
18+
public LocalizeExtension()
19+
{
20+
}
21+
22+
public LocalizeExtension(string key)
23+
{
24+
Key = key;
25+
}
26+
27+
public override object ProvideValue(IServiceProvider serviceProvider)
28+
{
29+
// Create a proxy that will notify when language changes
30+
var proxy = new LocalizationProxy(Key);
31+
32+
// Create binding to the proxy's Value property
33+
var binding = new Avalonia.Data.Binding
34+
{
35+
Source = proxy,
36+
Path = "Value",
37+
Mode = BindingMode.OneWay
38+
};
39+
40+
return binding;
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Value converter for binding localized strings
46+
/// Usage: Text="{Binding SomeKey, Converter={StaticResource LocalizeConverter}}"
47+
/// </summary>
48+
public class LocalizeConverter : IValueConverter
49+
{
50+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
51+
{
52+
if (value is string key)
53+
{
54+
return LocalizationService.Instance[key];
55+
}
56+
return value;
57+
}
58+
59+
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
60+
{
61+
throw new NotImplementedException();
62+
}
63+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.ComponentModel;
2+
using System.Runtime.CompilerServices;
3+
using SharpCoreDB.Viewer.Services;
4+
5+
namespace SharpCoreDB.Viewer.Helpers;
6+
7+
/// <summary>
8+
/// Proxy class that exposes localized strings as bindable properties
9+
/// </summary>
10+
public class LocalizationProxy : INotifyPropertyChanged
11+
{
12+
private readonly string _key;
13+
private readonly LocalizationService _localization = LocalizationService.Instance;
14+
15+
public event PropertyChangedEventHandler? PropertyChanged;
16+
17+
public LocalizationProxy(string key)
18+
{
19+
_key = key;
20+
21+
// Subscribe to language changes
22+
_localization.LanguageChanged += (s, e) =>
23+
{
24+
OnPropertyChanged(nameof(Value));
25+
};
26+
}
27+
28+
public string Value => _localization[_key];
29+
30+
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
31+
{
32+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
33+
}
34+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace SharpCoreDB.Viewer.Models;
2+
3+
/// <summary>
4+
/// Application settings model
5+
/// </summary>
6+
public class AppSettings
7+
{
8+
/// <summary>
9+
/// Current UI language (e.g., "en-US", "nl-NL")
10+
/// </summary>
11+
public string Language { get; set; } = "en-US";
12+
13+
/// <summary>
14+
/// Current theme variant ("Light" or "Dark")
15+
/// </summary>
16+
public string Theme { get; set; } = "Light";
17+
}

SharpCoreDB.Viewer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace SharpCoreDB.Viewer;
55

6-
sealed class Program
6+
static class Program
77
{
88
// Initialization code. Don't use any Avalonia, third-party APIs or any
99
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"AppTitle": "SharpCoreDB Viewer",
3+
"Connect": "Connect",
4+
"Disconnect": "Disconnect",
5+
"Execute": "Execute",
6+
"NotConnected": "Not connected",
7+
"DatabaseTables": "Database Tables",
8+
"QueryEditor": "SQL Query Editor",
9+
"QueryResults": "Query Results",
10+
"Ready": "Ready",
11+
"WelcomeTitle": "SharpCoreDB Viewer",
12+
"WelcomeMessage": "Click 'Connect' to open a database",
13+
"ConnectToDatabase": "Connect to Database",
14+
"QueryPlaceholder": "Enter SQL query here...",
15+
"ResultsPlaceholder": "Execute a query to see results",
16+
"ConnectionDialogTitle": "Connect to SharpCoreDB",
17+
"ConnectionDialogSubtitle": "Open an existing database or create a new one",
18+
"DatabasePath": "Database Path:",
19+
"DatabasePathPlaceholder": "C:\\data\\mydb.scdb or database directory",
20+
"Browse": "Browse...",
21+
"New": "New...",
22+
"MasterPassword": "Master Password:",
23+
"PasswordPlaceholder": "Enter database password",
24+
"Cancel": "Cancel",
25+
"Connecting": "Connecting...",
26+
"Save": "Save",
27+
"ConnectTooltip": "Connect to existing database",
28+
"DisconnectTooltip": "Disconnect from database",
29+
"ExecuteTooltip": "Execute SQL query (F5)",
30+
"BrowseTooltip": "Browse for existing database",
31+
"NewTooltip": "Create new database",
32+
"ErrorDatabasePathRequired": "Please enter a database path",
33+
"ErrorPasswordRequired": "Please enter a password",
34+
"ErrorDatabaseNotFound": "Database path does not exist",
35+
"ErrorConnectionFailed": "Connection failed: {0}",
36+
"ErrorTableLoadFailed": "Failed to load tables: {0}",
37+
"ConnectedTo": "Connected to: {0}",
38+
"SelectNewDatabaseLocation": "Select Location for New SharpCoreDB Database",
39+
"SettingsTitle": "Settings",
40+
"LanguageLabel": "Language",
41+
"LanguageDescription": "Select your preferred language. Changes will take effect immediately.",
42+
"ThemeLabel": "Theme",
43+
"ThemeDescription": "Choose between light and dark theme.",
44+
"FileMenu": "File",
45+
"SettingsMenu": "Settings",
46+
"ExitMenu": "Exit"
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"AppTitle": "SharpCoreDB Viewer",
3+
"Connect": "Verbinden",
4+
"Disconnect": "Verbinding verbreken",
5+
"Execute": "Uitvoeren",
6+
"NotConnected": "Niet verbonden",
7+
"DatabaseTables": "Database Tabellen",
8+
"QueryEditor": "SQL Query Editor",
9+
"QueryResults": "Query Resultaten",
10+
"Ready": "Gereed",
11+
"WelcomeTitle": "SharpCoreDB Viewer",
12+
"WelcomeMessage": "Klik op 'Verbinden' om een database te openen",
13+
"ConnectToDatabase": "Verbinden met Database",
14+
"QueryPlaceholder": "Voer SQL query hier in...",
15+
"ResultsPlaceholder": "Voer een query uit om resultaten te zien",
16+
"ConnectionDialogTitle": "Verbinden met SharpCoreDB",
17+
"ConnectionDialogSubtitle": "Open een bestaande database of maak een nieuwe aan",
18+
"DatabasePath": "Database Pad:",
19+
"DatabasePathPlaceholder": "C:\\data\\mijndb.scdb of database map",
20+
"Browse": "Bladeren...",
21+
"New": "Nieuw...",
22+
"MasterPassword": "Hoofdwachtwoord:",
23+
"PasswordPlaceholder": "Voer database wachtwoord in",
24+
"Cancel": "Annuleren",
25+
"Connecting": "Verbinden...",
26+
"Save": "Opslaan",
27+
"ConnectTooltip": "Verbind met bestaande database",
28+
"DisconnectTooltip": "Verbreek verbinding met database",
29+
"ExecuteTooltip": "Voer SQL query uit (F5)",
30+
"BrowseTooltip": "Blader naar bestaande database",
31+
"NewTooltip": "Maak nieuwe database aan",
32+
"ErrorDatabasePathRequired": "Voer een database pad in",
33+
"ErrorPasswordRequired": "Voer een wachtwoord in",
34+
"ErrorDatabaseNotFound": "Database pad bestaat niet",
35+
"ErrorConnectionFailed": "Verbinding mislukt: {0}",
36+
"ErrorTableLoadFailed": "Laden van tabellen mislukt: {0}",
37+
"ConnectedTo": "Verbonden met: {0}",
38+
"SelectNewDatabaseLocation": "Selecteer Locatie voor Nieuwe SharpCoreDB Database",
39+
"SettingsTitle": "Instellingen",
40+
"LanguageLabel": "Taal",
41+
"LanguageDescription": "Selecteer uw voorkeurstaal. Wijzigingen worden direct toegepast.",
42+
"ThemeLabel": "Thema",
43+
"ThemeDescription": "Kies tussen licht en donker thema.",
44+
"FileMenu": "Bestand",
45+
"SettingsMenu": "Instellingen",
46+
"ExitMenu": "Afsluiten"
47+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System.Globalization;
2+
using System.Text.Json;
3+
using System.ComponentModel;
4+
using System.Runtime.CompilerServices;
5+
using System.Diagnostics;
6+
using System.Reflection;
7+
8+
namespace SharpCoreDB.Viewer.Services;
9+
10+
/// <summary>
11+
/// Localization service for the SharpCoreDB Viewer with reactive property support
12+
/// </summary>
13+
public class LocalizationService : INotifyPropertyChanged
14+
{
15+
private static LocalizationService? _instance;
16+
private Dictionary<string, string> _strings = [];
17+
private CultureInfo _currentCulture;
18+
19+
public static LocalizationService Instance => _instance ??= new LocalizationService();
20+
21+
public event PropertyChangedEventHandler? PropertyChanged;
22+
public event EventHandler? LanguageChanged;
23+
24+
private LocalizationService()
25+
{
26+
_currentCulture = CultureInfo.CurrentUICulture;
27+
Debug.WriteLine($"[Localization] Service initialized with culture: {_currentCulture.Name}");
28+
LoadLanguage(_currentCulture.Name);
29+
}
30+
31+
public string this[string key] => _strings.TryGetValue(key, out var value) ? value : key;
32+
33+
public void SetLanguage(string cultureName)
34+
{
35+
Debug.WriteLine($"[Localization] SetLanguage called with: {cultureName}");
36+
37+
_currentCulture = new CultureInfo(cultureName);
38+
CultureInfo.CurrentUICulture = _currentCulture;
39+
LoadLanguage(cultureName);
40+
41+
Debug.WriteLine($"[Localization] Language changed to: {cultureName}, firing events...");
42+
43+
// Notify all properties have changed
44+
OnPropertyChanged(string.Empty);
45+
LanguageChanged?.Invoke(this, EventArgs.Empty);
46+
47+
Debug.WriteLine($"[Localization] LanguageChanged event fired. Subscribers: {LanguageChanged?.GetInvocationList().Length ?? 0}");
48+
}
49+
50+
private void LoadLanguage(string cultureName)
51+
{
52+
Debug.WriteLine($"[Localization] LoadLanguage called for: {cultureName}");
53+
54+
// Try specific culture first (e.g., nl-NL)
55+
if (!TryLoadLanguageFile(cultureName))
56+
{
57+
// Fall back to language only (e.g., nl)
58+
var language = cultureName.Split('-')[0];
59+
Debug.WriteLine($"[Localization] Trying fallback language: {language}");
60+
if (!TryLoadLanguageFile(language))
61+
{
62+
// Fall back to en-US
63+
Debug.WriteLine("[Localization] Falling back to en-US");
64+
TryLoadLanguageFile("en-US");
65+
}
66+
}
67+
}
68+
69+
private bool TryLoadLanguageFile(string cultureName)
70+
{
71+
// First try embedded resources
72+
var resourceName = $"SharpCoreDB.Viewer.Resources.Strings.{cultureName}.json";
73+
Debug.WriteLine($"[Localization] Attempting to load resource: {resourceName}");
74+
75+
var assembly = typeof(LocalizationService).Assembly;
76+
77+
// List all embedded resources for debugging
78+
var allResources = assembly.GetManifestResourceNames();
79+
Debug.WriteLine($"[Localization] Available manifest resources ({allResources.Length}): {string.Join(", ", allResources)}");
80+
81+
using var stream = assembly.GetManifestResourceStream(resourceName);
82+
if (stream != null)
83+
{
84+
_strings = JsonSerializer.Deserialize<Dictionary<string, string>>(stream) ?? new();
85+
Debug.WriteLine($"[Localization] ? Loaded {_strings.Count} strings from embedded resource for culture {cultureName}");
86+
87+
var sample = _strings.Take(3).Select(kvp => $"{kvp.Key}={kvp.Value}");
88+
Debug.WriteLine($"[Localization] Sample strings: {string.Join(", ", sample)}");
89+
return true;
90+
}
91+
92+
// Fallback: Try to load from file system (relative to executable)
93+
var exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "";
94+
var filePath = Path.Combine(exeDir, "Resources", $"Strings.{cultureName}.json");
95+
96+
Debug.WriteLine($"[Localization] Trying file system: {filePath}");
97+
98+
if (File.Exists(filePath))
99+
{
100+
var json = File.ReadAllText(filePath);
101+
_strings = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new();
102+
Debug.WriteLine($"[Localization] ? Loaded {_strings.Count} strings from file for culture {cultureName}");
103+
104+
var sample = _strings.Take(3).Select(kvp => $"{kvp.Key}={kvp.Value}");
105+
Debug.WriteLine($"[Localization] Sample strings: {string.Join(", ", sample)}");
106+
return true;
107+
}
108+
109+
Debug.WriteLine($"[Localization] ? Could not find resource or file for culture: {cultureName}");
110+
return false;
111+
}
112+
113+
public string Format(string key, params object[] args)
114+
{
115+
var template = this[key];
116+
return string.Format(template, args);
117+
}
118+
119+
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
120+
{
121+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
122+
}
123+
}

0 commit comments

Comments
 (0)