- git status - pretty much self-explanatory, but basically shows you the current branch and whether it is up-to-date with the remote branch, as well as and untracked files or uncommitted changes.
- git add -A - adds files to the git index, -A is the option for "all files".
- git commit -m "commit message" - Commits the current changes with the commit message provided (-m is the message option).
mandag den 6. juni 2022
Adding a project to GitHub
onsdag den 1. juni 2022
Exploring the capabilities of .NET MAUI part 2: Saving and loading language selection in app preferences, skipping selection page if language was loaded from settings
In my last post I briefly touched on how you can implement localization resources in a .NET MAUI app.
By default, .NET will try to find localization resource that corresponds to that of the system that the app is running on, which is pretty convenient.
For this post, however, I wanted to implement a list of languages so that the user can select a language from a language selection page at first app launch, after which the selected language will be saved to app preferences, which is built directly into MAUI for all platforms that it supports.
So, I will implement a ContentPage which its own ViewModel (which is constructor-injected into the ContentPage using .NETs built-in depdendency-injection), which will serve as the first page that is shown only if the preferences contains no saved language.
First things first, I started with creating a simple Language model, which I placed in a "Models" folder in the solution:
namespace localization_demo.Models; public class Language { public Language(string identifier, string name) { Identifier = identifier; Name = name; } public string Identifier { get; set; } public string Name { get; set; } public override string ToString() { return Name; } }After this I began writing the code of the ViewModel, but explaining every line of it in text would make this post a bit longer than I would like, so I've instead placed some comments explaining the basic idea:
namespace localization_demo.ViewModels; public class ChooseLanguageViewModel { public ChooseLanguageViewModel() { // instantiate ObservableCollection of Language objects Languages = new ObservableCollection<Language> { new Language("en-US", "English"), new Language("da-DK", "Danish"), }; // try to load saved language LoadSavedLanguagePreferences(); } // this property will serve as the collection which we will show in our contentpage public ObservableCollection<Language> Languages { get; private set; } // contains the current selection private Language _selectedLanguage; public Language SelectedLanguage { get => _selectedLanguage; set { _selectedLanguage = value; SetLanguage(); } } private void SetLanguage() { SaveLanguagePreferences(SelectedLanguage); // change locale: Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); CultureInfo.CurrentCulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); CultureInfo.CurrentUICulture = new System.Globalization.CultureInfo(SelectedLanguage.Identifier); // continue GoToMainPage(); } private void LoadSavedLanguagePreferences() { if (!Preferences.ContainsKey("locale")) { return; } var savedLocale = Preferences.Get("locale", ""); if (string.IsNullOrEmpty(savedLocale)) { return; } var correspondingLanguage = Languages.Where(x => string.Compare(x.Identifier, savedLocale) == 0); if (correspondingLanguage.Count() != 1) { // something went wrong here return; } SelectedLanguage = correspondingLanguage.First(); } private void SaveLanguagePreferences(Language language) { if (language == null) { return; } // check if preferences is already set if (string.Compare(Preferences.Get("locale", ""), language.Identifier) == 0) { return; } Preferences.Set("locale", language.Identifier); } // //// does not preserve backstack private async void GoToMainPage() => await Shell.Current.GoToAsync($"////{nameof(MainPage)}"); }And then I implemented the ContentPage like so, notice that it sets BindingContext in the constructor to the injected ViewModel in order to bind to objects in xml etc:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:vm="clr-namespace:localization_demo.ViewModels" xmlns:model="clr-namespace:localization_demo.Models" x:DataType="vm:ChooseLanguageViewModel" x:Class="localization_demo.ChooseLanguagePage" xmlns:res="clr-namespace:localization_demo.Resources.Strings"> <VerticalStackLayout Spacing="25" Padding="30" HorizontalOptions="Center"> <Label Text="{x:Static res:AppResource.Choose_language}" SemanticProperties.HeadingLevel="Level1" FontSize="32" HorizontalOptions="Center" /> <CollectionView ItemsSource="{Binding Languages}" SelectionMode="Single" SelectedItem="{Binding SelectedLanguage}" > <CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Language"> <Grid Padding="10"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Label Grid.Column="1" Text="{Binding Name}" FontSize="24" /> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </VerticalStackLayout> </ContentPage>
using localization_demo.ViewModels; namespace localization_demo; public partial class ChooseLanguagePage : ContentPage { private readonly ChooseLanguageViewModel _viewModel; public ChooseLanguagePage(ChooseLanguageViewModel viewModel) { InitializeComponent(); BindingContext = _viewModel = viewModel; } }However, in order to use dependency-injection in this way, we need to register the types in the dependency-injection system in the CreateMauiApp method, so it ends up looking like this:
public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); builder.Services.AddSingleton<ChooseLanguageViewModel>(); builder.Services.AddTransient<ChooseLanguagePage>(); return builder.Build(); } }And then lastly, we need to add the new ChooseLanguagePage to our AppShell as a ShellItem, where we also specify the route to it, like so:
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="localization_demo.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:localization_demo" xmlns:res="clr-namespace:localization_demo.Resources.Strings" Shell.FlyoutBehavior="Disabled"> <ShellContent ContentTemplate="{DataTemplate local:ChooseLanguagePage}" Route="ChooseLanguagePage" /> <ShellContent ContentTemplate="{DataTemplate local:MainPage}" Route="MainPage" /> </Shell>Now this in by no means a pretty GUI or anything, but the results should be sufficient in terms of being a PoC:
After selecting "Danish", we are redirected to the MainPage of the template app, with out localized strings: