tools/installer: move language to external file

This moves all language used in the installer to an embedded JSON file
to allow for customisation per game.
This commit is contained in:
lahm86 2025-03-25 11:38:46 +00:00
parent e16fcda94b
commit 365cff79c1
23 changed files with 170 additions and 97 deletions

View file

@ -6,7 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:TRX_InstallerLib.Models" xmlns:models="clr-namespace:TRX_InstallerLib.Models"
d:DataContext="{d:DesignInstance Type=models:FinishStep}" d:DataContext="{d:DesignInstance Type=models:FinishStep}"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
@ -14,19 +14,17 @@
</UserControl.Resources> </UserControl.Resources>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}"> <TextBlock
Step 4: Done Style="{StaticResource heading}"
</TextBlock> Text="{Binding ViewText[step_finish_heading]}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12" TextWrapping="Wrap"> <TextBlock
Installation complete. To configure more advanced features, you can edit the JSON files in the cfg/ directory with a text editor. VerticalAlignment="Center"
</TextBlock> Margin="0,0,0,24"
TextWrapping="Wrap"
Text="{Binding ViewText[step_finish_content]}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12"> <CheckBox IsChecked="{Binding FinishSettings.OpenGameDirectory}" Content="{Binding ViewText[step_finish_open_directory]}" Margin="0,0,0,12" />
Happy raiding :) <CheckBox IsChecked="{Binding FinishSettings.LaunchGame}" Content="{Binding ViewText[step_finish_open_game]}" Margin="0,0,0,12" />
</TextBlock>
<CheckBox IsChecked="{Binding FinishSettings.OpenGameDirectory}" Content="Open game directory after closing this window" Margin="0,0,0,12" />
<CheckBox IsChecked="{Binding FinishSettings.LaunchGame}" Content="Launch the game after closing this window" Margin="0,0,0,12" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View file

@ -7,7 +7,7 @@
xmlns:models="clr-namespace:TRX_InstallerLib.Models" xmlns:models="clr-namespace:TRX_InstallerLib.Models"
xmlns:utils="clr-namespace:TRX_InstallerLib.Utils" xmlns:utils="clr-namespace:TRX_InstallerLib.Utils"
d:DataContext="{d:DesignInstance Type=models:InstallSettingsStep}" d:DataContext="{d:DesignInstance Type=models:InstallSettingsStep}"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
@ -20,9 +20,9 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}"> <TextBlock
Step 2: Installation options Style="{StaticResource heading}"
</TextBlock> Text="{Binding ViewText[step_settings_heading]}"/>
<ScrollViewer Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ScrollViewer.Template> <ScrollViewer.Template>
@ -82,15 +82,10 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.DownloadMusic}" IsEnabled="{Binding InstallSettings.IsDownloadingMusicNeeded}"> <CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.DownloadMusic}" IsEnabled="{Binding InstallSettings.IsDownloadingMusicNeeded}">
<TextBlock TextWrapping="Wrap"> <TextBlock TextWrapping="Wrap">
Download music tracks <Run Text="{Binding ViewText[step_settings_music_heading]}"/>
<Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingMusicNeeded, Converter={utils:ConditionalMarkupConverter TrueValue='', FalseValue='(already found)'}, Mode=OneWay}" /> <Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingMusicNeeded, Converter={utils:ConditionalViewTextConverter TrueValue='', FalseValue='label_already_found'}, Mode=OneWay}" />
<LineBreak /> <LineBreak />
<Run Style="{StaticResource small}"> <Run Style="{StaticResource small}" Text="{Binding ViewText[step_settings_music_content]}"/>
This option lets you download compatible music files for the game
automatically (60 MB). The legality of these files is disputable;
the most legal way to import the music to PC is to obtain them from
your own source - TR2 supports FLAC, OOG, MP3 and WAV files.
</Run>
</TextBlock> </TextBlock>
</CheckBox> </CheckBox>
@ -100,33 +95,31 @@
<utils:ComparisonConverter x:Key="ComparisonConverter" /> <utils:ComparisonConverter x:Key="ComparisonConverter" />
</StackPanel.Resources> </StackPanel.Resources>
<TextBlock TextWrapping="Wrap" Margin="0,0,0,6"> <TextBlock TextWrapping="Wrap" Margin="0,0,0,6">
Download Unfinished Business expansion pack <Run Text="{Binding ViewText[step_settings_expansion_heading]}"/>
<Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingExpansionNeeded, Converter={utils:ConditionalMarkupConverter TrueValue='', FalseValue='(already found)'}, Mode=OneWay}" /> <Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingExpansionNeeded, Converter={utils:ConditionalViewTextConverter TrueValue='', FalseValue='label_already_found'}, Mode=OneWay}" />
<LineBreak /> <LineBreak />
<Run Style="{StaticResource small}"> <Run Style="{StaticResource small}" Text="{Binding ViewText[step_settings_expansion_content]}"/>
The Unfinished Business expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (6 MB).
</Run>
</TextBlock> </TextBlock>
<RadioButton IsEnabled="{Binding InstallSettings.DownloadExpansionPack}" Style="{StaticResource small}" Margin="0,0,0,6" Content="Fan-made edition (includes music triggers)" <RadioButton IsEnabled="{Binding InstallSettings.DownloadExpansionPack}" Style="{StaticResource small}" Margin="0,0,0,6" Content="{Binding ViewText[step_settings_expansion_music]}"
IsChecked="{Binding Path=InstallSettings.ExpansionPackType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:ExpansionPackType.Music}}"/> IsChecked="{Binding Path=InstallSettings.ExpansionPackType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:ExpansionPackType.Music}}"/>
<RadioButton IsEnabled="{Binding InstallSettings.DownloadExpansionPack}" Style="{StaticResource small}" Margin="0,0,0,6" Content="Original edition (does not include music triggers)" <RadioButton IsEnabled="{Binding InstallSettings.DownloadExpansionPack}" Style="{StaticResource small}" Margin="0,0,0,6" Content="{Binding ViewText[step_settings_expansion_vanilla]}"
IsChecked="{Binding Path=InstallSettings.ExpansionPackType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:ExpansionPackType.Vanilla}}"/> IsChecked="{Binding Path=InstallSettings.ExpansionPackType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:ExpansionPackType.Vanilla}}"/>
</StackPanel> </StackPanel>
</CheckBox> </CheckBox>
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.ImportSaves}" IsEnabled="{Binding InstallSettings.InstallSource.IsImportingSavesSupported}"> <CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.ImportSaves}" IsEnabled="{Binding InstallSettings.InstallSource.IsImportingSavesSupported}">
<TextBlock TextWrapping="Wrap"> <TextBlock TextWrapping="Wrap">
Import saves <Run Text="{Binding ViewText[step_settings_saves_header]}"/>
<LineBreak /> <LineBreak />
<Run Style="{StaticResource small}"> <Run Style="{StaticResource small}" Text="{Binding ViewText[step_settings_saves_content]}"/>
Imports existing savegame files. Only TombATI and TR1X savegame format is supported at this time.
</Run>
</TextBlock> </TextBlock>
</CheckBox> </CheckBox>
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.CreateDesktopShortcut}"> <CheckBox
Create desktop shortcut VerticalAlignment="Center"
</CheckBox> Margin="0,0,0,12"
IsChecked="{Binding InstallSettings.CreateDesktopShortcut}"
Content="{Binding ViewText[step_settings_shortcut_heading]}"/>
<Separator /> <Separator />
@ -136,9 +129,9 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Column="0" VerticalAlignment="Center" Margin="0,0,12,0" Padding="0" Content="Destination folder:" /> <Label Grid.Column="0" VerticalAlignment="Center" Margin="0,0,12,0" Padding="0" Content="{Binding ViewText[label_destination_folder]}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding InstallSettings.TargetDirectory}" TextTrimming="CharacterEllipsis" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding InstallSettings.TargetDirectory}" TextTrimming="CharacterEllipsis" />
<Button Grid.Column="2" VerticalAlignment="Center" Margin="12,0,0,0" Command="{Binding ChooseLocationCommand}" Content="Change..." /> <Button Grid.Column="2" VerticalAlignment="Center" Margin="12,0,0,0" Command="{Binding ChooseLocationCommand}" Content="{Binding ViewText[command_change]}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View file

@ -50,8 +50,8 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Padding="0" Margin="0,-3,12,3" VerticalAlignment="Top" Style="{StaticResource subHeading}" Text="{Binding InstallSource.SourceName}" Height="16" /> <TextBlock Grid.Column="0" Padding="0" Margin="0,-3,12,3" VerticalAlignment="Top" Style="{StaticResource subHeading}" Text="{Binding InstallSource.SourceName}" Height="16" />
<TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToVisibleConverter}}" Text="Found" /> <TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToVisibleConverter}}" Text="{Binding ViewText[label_found]}" />
<TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingNotFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToHiddenConverter}}" Text="Not found" /> <TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingNotFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToHiddenConverter}}" Text="{Binding ViewText[label_not_found]}" />
</Grid> </Grid>
<Grid Grid.Row="1" Grid.Column="1"> <Grid Grid.Row="1" Grid.Column="1">
@ -59,9 +59,12 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding SourceDirectory, TargetNullValue='(no folder selected)'}" TextTrimming="CharacterEllipsis" /> <TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding ViewText[label_folder_not_selected]}" Visibility="{Binding IsSourceDirectoryDefined, Converter={StaticResource BoolToHiddenConverter}}" />
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding SourceDirectory}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="6,0,0,0"> <TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="6,0,0,0">
<Hyperlink Command="{Binding ChooseLocationCommand}" >(change)</Hyperlink> <Hyperlink Command="{Binding ChooseLocationCommand}">
<Run Text="{Binding ViewText[command_change_link]}"/>
</Hyperlink>
</TextBlock> </TextBlock>
</Grid> </Grid>
</Grid> </Grid>

View file

@ -24,9 +24,9 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical"> <StackPanel Grid.Row="0" Orientation="Vertical">
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}"> <TextBlock
Step 3: Installing Style="{StaticResource heading}"
</TextBlock> Text="{Binding ViewText[step_install_heading]}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12" Text="{Binding Description}" /> <TextBlock VerticalAlignment="Center" Margin="0,0,0,12" Text="{Binding Description}" />

View file

@ -7,8 +7,8 @@
xmlns:controls="clr-namespace:TRX_InstallerLib.Controls" xmlns:controls="clr-namespace:TRX_InstallerLib.Controls"
xmlns:models="clr-namespace:TRX_InstallerLib.Models" xmlns:models="clr-namespace:TRX_InstallerLib.Models"
xmlns:utils="clr-namespace:TRX_InstallerLib.Utils" xmlns:utils="clr-namespace:TRX_InstallerLib.Utils"
d:DataContext="{d:DesignInstance Type=models:MainWindowViewModel}" d:DataContext="{d:DesignInstance Type=models:SourceStep}"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
@ -18,7 +18,7 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="30" /> <RowDefinition Height="Auto" MinHeight="30" />
@ -29,17 +29,16 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}"> <TextBlock
Step 1: Choose installation source Style="{StaticResource heading}"
</TextBlock> Text="{Binding ViewText[step_source_heading]}"/>
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,10"> <TextBlock
TR1X requires original game files to run. Grid.Row="1"
<LineBreak /> TextWrapping="Wrap"
Please choose the source location where to install the data files from. VerticalAlignment="Center"
<LineBreak /> Margin="0,0,0,10"
If you're upgrading an existing installation, please choose TR1X. Text="{Binding ViewText[step_source_content]}"/>
</TextBlock>
<ListView <ListView
BorderThickness="0" BorderThickness="0"

View file

@ -90,17 +90,17 @@
<Button <Button
Command="{Binding GoToPreviousStepCommand}" Command="{Binding GoToPreviousStepCommand}"
Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}" Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}"
Content="_Back" /> Content="{Binding ViewText[command_back]}" />
<Button <Button
Grid.Column="1" Grid.Column="1"
Command="{Binding GoToNextStepCommand}" Command="{Binding GoToNextStepCommand}"
Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}" Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}"
Content="_Next" /> Content="{Binding ViewText[command_next]}" />
<Button <Button
Grid.Column="2" Grid.Column="2"
Command="{Binding CloseWindowCommand}" Command="{Binding CloseWindowCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Content="{Binding IsFinalStep, Converter={utils:ConditionalMarkupConverter TrueValue='_Close', FalseValue='_Cancel'}}" /> Content="{Binding IsFinalStep, Converter={utils:ConditionalViewTextConverter TrueValue='command_close', FalseValue='command_cancel'}}" />
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>

View file

@ -52,7 +52,7 @@ public class InstallExecutor
CreateDesktopShortcut(_settings.TargetDirectory); CreateDesktopShortcut(_settings.TargetDirectory);
} }
progress.Report(new InstallProgress { Description = "Finished", Finished = true }); progress.Report(new InstallProgress { Description = Language.Instance.Controls!["progress_finished"], Finished = true });
} }
protected async Task CopyOriginalGameFiles(string sourceDirectory, string targetDirectory, IProgress<InstallProgress> progress) protected async Task CopyOriginalGameFiles(string sourceDirectory, string targetDirectory, IProgress<InstallProgress> progress)
@ -72,11 +72,11 @@ public class InstallExecutor
{ {
CurrentValue = 0, CurrentValue = 0,
MaximumValue = 1, MaximumValue = 1,
Description = "Opening embedded ZIP", Description = Language.Instance.Controls!["progress_opening_zip"],
}); });
using var stream = AssemblyUtils.GetResourceStream("Resources.release.zip", false) using var stream = AssemblyUtils.GetResourceStream("Resources.release.zip", false)
?? throw new ApplicationException($"Could not open embedded ZIP."); ?? throw new ApplicationException(Language.Instance.Controls!["progress_zip_failure"]);
await InstallUtils.ExtractZip(stream, targetDirectory, progress, overwrite: true); await InstallUtils.ExtractZip(stream, targetDirectory, progress, overwrite: true);
} }

View file

@ -26,7 +26,7 @@ public static class InstallUtils
{ {
try try
{ {
progress.Report(new InstallProgress { Description = "Scanning directory" }); progress.Report(new InstallProgress { Description = Language.Instance.Controls!["progress_scanning"] });
var files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories); var files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories);
var currentProgress = 0; var currentProgress = 0;
var maximumProgress = files.Length; var maximumProgress = files.Length;
@ -45,7 +45,7 @@ public static class InstallUtils
{ {
CurrentValue = currentProgress, CurrentValue = currentProgress,
MaximumValue = maximumProgress, MaximumValue = maximumProgress,
Description = $"Copying {relPath}", Description = string.Format(Language.Instance.Controls!["progress_copying"], relPath),
}); });
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!); Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
await Task.Run(() => File.Copy(sourcePath, targetPath, true)); await Task.Run(() => File.Copy(sourcePath, targetPath, true));
@ -56,7 +56,7 @@ public static class InstallUtils
{ {
CurrentValue = currentProgress, CurrentValue = currentProgress,
MaximumValue = maximumProgress, MaximumValue = maximumProgress,
Description = $"Copying {relPath} - skipped", Description = string.Format(Language.Instance.Controls!["progress_skipped"], relPath),
}); });
} }
@ -65,7 +65,7 @@ public static class InstallUtils
} }
catch (Exception e) catch (Exception e)
{ {
throw new ApplicationException($"Could not extract ZIP:\n{e.Message}"); throw new ApplicationException(e.Message);
} }
} }
@ -79,14 +79,14 @@ public static class InstallUtils
public static async Task<byte[]> DownloadFile(string url, IProgress<InstallProgress> progress) public static async Task<byte[]> DownloadFile(string url, IProgress<InstallProgress> progress)
{ {
HttpProgressClient wc = new(); HttpProgressClient wc = new();
progress.Report(new InstallProgress { Description = $"Initializing download of {url}" }); progress.Report(new InstallProgress { Description = string.Format(Language.Instance.Controls!["progress_init_download"], url) });
wc.DownloadProgressChanged += (totalBytesToReceive, bytesReceived) => wc.DownloadProgressChanged += (totalBytesToReceive, bytesReceived) =>
{ {
progress.Report(new InstallProgress progress.Report(new InstallProgress
{ {
CurrentValue = (int)bytesReceived, CurrentValue = (int)bytesReceived,
MaximumValue = (int)totalBytesToReceive, MaximumValue = (int)totalBytesToReceive,
Description = $"Downloading {url}", Description = string.Format(Language.Instance.Controls!["progress_downloading"], url),
}); });
}; };
return await wc.DownloadDataTaskAsync(new Uri(url)); return await wc.DownloadDataTaskAsync(new Uri(url));
@ -116,7 +116,7 @@ public static class InstallUtils
using var zip = new ZipArchive(stream); using var zip = new ZipArchive(stream);
progress.Report(new InstallProgress progress.Report(new InstallProgress
{ {
Description = "Scanning ZIP", Description = Language.Instance.Controls!["progress_scanning_zip"],
}); });
var currentProgress = 0; var currentProgress = 0;
var maximumProgress = zip.Entries.Count; var maximumProgress = zip.Entries.Count;
@ -140,7 +140,7 @@ public static class InstallUtils
{ {
CurrentValue = currentProgress, CurrentValue = currentProgress,
MaximumValue = maximumProgress, MaximumValue = maximumProgress,
Description = $"Extracting {entry.FullName}", Description = string.Format(Language.Instance.Controls!["progress_extracting"], entry.FullName),
}); });
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!); Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
@ -152,7 +152,7 @@ public static class InstallUtils
{ {
CurrentValue = currentProgress, CurrentValue = currentProgress,
MaximumValue = maximumProgress, MaximumValue = maximumProgress,
Description = $"Extracting {entry.FullName} - skipped", Description = string.Format(Language.Instance.Controls!["progress_extracting_skipped"], entry.FullName),
}); });
} }
@ -161,7 +161,7 @@ public static class InstallUtils
} }
catch (Exception e) catch (Exception e)
{ {
throw new ApplicationException($"Could not extract ZIP:\n{e.Message}"); throw new ApplicationException(e.Message);
} }
} }

View file

@ -2,7 +2,7 @@ using TRX_InstallerLib.Utils;
namespace TRX_InstallerLib.Models; namespace TRX_InstallerLib.Models;
public class FinishStep : BaseNotifyPropertyChanged, IStep public class FinishStep : BaseLanguageViewModel, IStep
{ {
public FinishStep(FinishSettings finishSettings) public FinishStep(FinishSettings finishSettings)
{ {

View file

@ -3,7 +3,7 @@ using TRX_InstallerLib.Utils;
namespace TRX_InstallerLib.Models; namespace TRX_InstallerLib.Models;
public class InstallSettingsStep : BaseNotifyPropertyChanged, IStep public class InstallSettingsStep : BaseLanguageViewModel, IStep
{ {
public InstallSettingsStep(InstallSettings installSettings) public InstallSettingsStep(InstallSettings installSettings)
{ {
@ -24,7 +24,7 @@ public class InstallSettingsStep : BaseNotifyPropertyChanged, IStep
public ICommand ChooseLocationCommand public ICommand ChooseLocationCommand
{ {
get => _chooseLocationCommand ??= new RelayCommand(ChooseLocation); get => _chooseLocationCommand ??= new RelayCommand(ChooseLocation);
} }
private void ChooseLocation() private void ChooseLocation()
{ {

View file

@ -4,7 +4,7 @@ using TRX_InstallerLib.Utils;
namespace TRX_InstallerLib.Models; namespace TRX_InstallerLib.Models;
public class InstallSourceViewModel : BaseNotifyPropertyChanged public class InstallSourceViewModel : BaseLanguageViewModel
{ {
public InstallSourceViewModel(IInstallSource source) public InstallSourceViewModel(IInstallSource source)
{ {
@ -38,6 +38,11 @@ public class InstallSourceViewModel : BaseNotifyPropertyChanged
} }
} }
public bool IsSourceDirectoryDefined
{
get => SourceDirectory != null;
}
public string? SourceDirectory public string? SourceDirectory
{ {
get => _sourceDirectory; get => _sourceDirectory;
@ -48,6 +53,7 @@ public class InstallSourceViewModel : BaseNotifyPropertyChanged
_sourceDirectory = value; _sourceDirectory = value;
NotifyPropertyChanged(); NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsAvailable)); NotifyPropertyChanged(nameof(IsAvailable));
NotifyPropertyChanged(nameof(IsSourceDirectoryDefined));
} }
} }
} }

View file

@ -4,7 +4,7 @@ using TRX_InstallerLib.Utils;
namespace TRX_InstallerLib.Models; namespace TRX_InstallerLib.Models;
public class InstallStep : BaseNotifyPropertyChanged, IStep public class InstallStep : BaseLanguageViewModel, IStep
{ {
public InstallStep(InstallSettings installSettings) public InstallStep(InstallSettings installSettings)
{ {

View file

@ -13,7 +13,7 @@ public class Language
public static Language Instance { get; private set; } public static Language Instance { get; private set; }
public Dictionary<string, string>? Controls { get; set; } public Dictionary<string, string>? Controls { get; set; }
static Language() static Language()
{ {
CultureInfo defaultCulture = CultureInfo.GetCultureInfo(_defaultCulture); CultureInfo defaultCulture = CultureInfo.GetCultureInfo(_defaultCulture);

View file

@ -4,7 +4,7 @@ using TRX_InstallerLib.Utils;
namespace TRX_InstallerLib.Models; namespace TRX_InstallerLib.Models;
public class SourceStep : BaseNotifyPropertyChanged, IStep public class SourceStep : BaseLanguageViewModel, IStep
{ {
public SourceStep(IEnumerable<IInstallSource> installSources) public SourceStep(IEnumerable<IInstallSource> installSources)
{ {

View file

@ -1,5 +1,56 @@
{ {
"Controls": { "Controls": {
"command_back": "_Back",
"command_next": "_Next",
"command_close": "_Close",
"command_cancel": "_Cancel",
"command_change": "C_hange...",
"command_change_link": "(change)",
"label_found": "Found",
"label_not_found": "Not found",
"label_already_found": "(already found)",
"label_folder_not_selected": "(no folder selected)",
"label_destination_folder": "Destination folder:",
"label_select_folder": "Choose directory",
"step_source_heading": "Step 1: Choose installation source",
"step_source_content": "Placeholder",
"step_settings_heading": "Step 2: Installation options",
"step_settings_music_heading": "Download music tracks",
"step_settings_music_content": "Placeholder",
"step_settings_expansion_heading": "Placeholder",
"step_settings_expansion_content": "Placeholder",
"step_settings_expansion_music": "Placeholder",
"step_settings_expansion_vanilla": "Placeholder",
"step_settings_saves_header": "Import saves",
"step_settings_saves_content": "Placeholder",
"step_settings_shortcut_heading": "Create desktop shortcut",
"step_install_heading": "Step 3: Installing",
"step_finish_heading": "Step 4: Done",
"step_finish_content": "Installation complete. To configure more advanced features, you can edit the JSON files in the cfg/ directory with a text editor.\n\nHappy raiding :)",
"step_finish_open_directory": "Open game directory after closing this window",
"step_finish_open_game": "Launch the game after closing this window",
"progress_scanning": "Scanning directory",
"progress_scanning_source": "Scanning the source directory",
"progress_preparing_extract": "Preparing to extract the ISO",
"progress_copying": "Copying {0}",
"progress_skipped": "Copying {0} - skipped",
"progress_init_download": "Initializing download of {0}",
"progress_downloading": "Downloading {0}",
"progress_opening_zip": "Opening embedded ZIP",
"progress_zip_failure": "Could not open embedded ZIP.",
"progress_scanning_zip": "Scanning ZIP",
"progress_extracting": "Extracting {0}",
"progress_extracting_skipped": "Extracting {0} - skipped",
"progress_converting_bin": "Converting BIN to ISO",
"progress_converting_bin_failure": "Could not convert BIN to ISO: {0}",
"progress_converting_iso_failure": "Could not open converted ISO: {0}",
"progress_track_write_failure": "Could not write to track file {0}: {1}",
"progress_track_seek_failure": "Could not seek to track location: {0}",
"progress_bin_failure": "Could not open BIN {0}: {1}",
"progress_cue_failure": "Could not read CUE {0}: {1}",
"progress_cue_empty": "Could not parse {0}: no tracks were found",
"progress_finished": "Finished",
"shortcut_signature_failure": "Invalid LNK signature",
"shortcut_target_failure": "Unable to determine link target path"
} }
} }

View file

@ -8,6 +8,9 @@
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}" /> <Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}" />
<Style x:Key="heading" TargetType="TextBlock"> <Style x:Key="heading" TargetType="TextBlock">
<Setter Property="FontSize" Value="20" /> <Setter Property="FontSize" Value="20" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,12" />
</Style> </Style>
<Style x:Key="subHeading" TargetType="TextBlock"> <Style x:Key="subHeading" TargetType="TextBlock">
<Setter Property="FontSize" Value="15" /> <Setter Property="FontSize" Value="15" />

View file

@ -9,9 +9,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Resources\const.json" /> <None Remove="Resources\const.json" />
<None Remove="Resources\Lang\en.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\const.json" /> <EmbeddedResource Include="Resources\const.json" />
<EmbeddedResource Include="Resources\Lang\en.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

View file

@ -4,12 +4,12 @@ using System.Windows.Markup;
namespace TRX_InstallerLib.Utils; namespace TRX_InstallerLib.Utils;
public sealed class ConditionalMarkupConverter : MarkupExtension, IValueConverter public class ConditionalMarkupConverter : MarkupExtension, IValueConverter
{ {
public object FalseValue { get; set; } = new(); public object FalseValue { get; set; } = new();
public object TrueValue { get; set; } = new(); public object TrueValue { get; set; } = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
return value is true ? TrueValue : FalseValue; return value is true ? TrueValue : FalseValue;
} }

View file

@ -0,0 +1,13 @@
using System.Globalization;
using TRX_InstallerLib.Models;
namespace TRX_InstallerLib.Utils;
public class ConditionalViewTextConverter : ConditionalMarkupConverter
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string result = base.Convert(value, targetType, parameter, culture).ToString()!;
return result.Length == 0 ? string.Empty : Language.Instance.Controls![result];
}
}

View file

@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using TRX_InstallerLib.Models;
namespace TRX_InstallerLib.Utils; namespace TRX_InstallerLib.Utils;
@ -19,7 +20,7 @@ public class CueFile
MatchCollection fileMatches = _fileGroupRegex.Matches(cueFileContent); MatchCollection fileMatches = _fileGroupRegex.Matches(cueFileContent);
if (fileMatches.Count == 0) if (fileMatches.Count == 0)
{ {
throw new ApplicationException($"Could not parse {cueFilePath}: no tracks were found"); throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_cue_empty"], cueFilePath));
} }
foreach (Match fileMatch in fileMatches.Cast<Match>()) foreach (Match fileMatch in fileMatches.Cast<Match>())
@ -29,7 +30,7 @@ public class CueFile
if (matches.Count == 0) if (matches.Count == 0)
{ {
throw new ApplicationException($"Could not parse {cueFilePath}: no tracks were found"); throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_cue_empty"], cueFilePath));
} }
CueTrack? track = null; CueTrack? track = null;

View file

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using TRX_InstallerLib.Models;
namespace TRX_InstallerLib.Utils; namespace TRX_InstallerLib.Utils;
@ -78,7 +79,7 @@ public class CueTrack
{ {
MaximumValue = (int)TotalBytes, MaximumValue = (int)TotalBytes,
CurrentValue = (int)convertedBytes, CurrentValue = (int)convertedBytes,
Description = "Converting BIN to ISO" Description = Language.Instance.Controls!["progress_converting_bin"],
}); });
} }
@ -87,14 +88,14 @@ public class CueTrack
} }
catch (Exception e) catch (Exception e)
{ {
throw new ApplicationException(string.Format(" Could not write to track file {0}: {1}", targetPath, e.Message)); throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_track_write_failure"], targetPath, e.Message));
} }
progress.Report(new InstallProgress progress.Report(new InstallProgress
{ {
MaximumValue = (int)TotalBytes, MaximumValue = (int)TotalBytes,
CurrentValue = (int)TotalBytes, CurrentValue = (int)TotalBytes,
Description = "Converting BIN to ISO", Description = Language.Instance.Controls!["progress_converting_bin"],
}); });
} }
@ -169,7 +170,7 @@ public class CueTrack
} }
catch (Exception e) catch (Exception e)
{ {
throw new ApplicationException($"Could not open BIN {BinFilePath}: {e.Message}"); throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_bin_failure"], BinFilePath, e.Message));
} }
try try
{ {
@ -177,7 +178,7 @@ public class CueTrack
} }
catch (Exception e) catch (Exception e)
{ {
throw new ApplicationException(string.Format("Could not seek to track location: {0}", e.Message)); throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_track_seek_failure"], e.Message));
} }
return fileStream; return fileStream;
} }

View file

@ -1,3 +1,5 @@
using TRX_InstallerLib.Models;
namespace TRX_InstallerLib.Utils; namespace TRX_InstallerLib.Utils;
public class FileBrowser public class FileBrowser
@ -6,7 +8,7 @@ public class FileBrowser
{ {
using var dlg = new FolderBrowserDialog() using var dlg = new FolderBrowserDialog()
{ {
Description = "Choose directory", Description = Language.Instance.Controls!["label_select_folder"],
SelectedPath = initialDirectory, SelectedPath = initialDirectory,
ShowNewFolderButton = true, ShowNewFolderButton = true,
}; };

View file

@ -2,6 +2,7 @@ using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using TRX_InstallerLib.Models;
namespace TRX_InstallerLib.Utils; namespace TRX_InstallerLib.Utils;
@ -146,7 +147,7 @@ public static class ShortcutUtils
var headerSize = br.ReadUInt32(); var headerSize = br.ReadUInt32();
if (headerSize != 0x4C) if (headerSize != 0x4C)
{ {
throw new ApplicationException("Invalid LNK signature"); throw new ApplicationException(Language.Instance.Controls!["shortcut_signature_failure"]);
} }
br.ReadBytes(0x10); // skip LinkCLSID br.ReadBytes(0x10); // skip LinkCLSID
@ -230,6 +231,6 @@ public static class ShortcutUtils
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), relativePath); return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), relativePath);
} }
throw new ApplicationException("Unable to determine link target path"); throw new ApplicationException(Language.Instance.Controls!["shortcut_target_failure"]);
} }
} }