Add localization support

This commit is contained in:
Pete Batard
2019-01-14 11:47:25 +00:00
parent fc31ef3f59
commit e4467b621b
3 changed files with 98 additions and 79 deletions

7
.editorconfig Normal file
View File

@@ -0,0 +1,7 @@
# indicate this is the root of the project
root = true
[*]
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8

63
.gitattributes vendored
View File

@@ -1,63 +1,2 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto * text=auto
*.ps1 eol=crlf
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

107
Frida.ps1
View File

@@ -1,4 +1,4 @@
# #
# Frida - The Full Retail ISO Download Application # Frida - The Full Retail ISO Download Application
# Copyright © 2019 Pete Batard <pete@akeo.ie> # Copyright © 2019 Pete Batard <pete@akeo.ie>
# ConvertTo-ImageSource: Copyright © 2016 Chris Carter # ConvertTo-ImageSource: Copyright © 2016 Chris Carter
@@ -17,6 +17,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# NB: You need a BOM on your .ps1 if you want Powershell to actually
# realise it should use Unicode for the UI controls and not ISO-8859-1
# Parameters # Parameters
param( param(
# (Optional) Name of a pipe the download URL should be sent to. # (Optional) Name of a pipe the download URL should be sent to.
@@ -28,9 +31,12 @@ param(
# (Optional) Path to the file that should be used for the UI icon. # (Optional) Path to the file that should be used for the UI icon.
[string]$Icon, [string]$Icon,
# (Optional) The title to display on the application window # (Optional) The title to display on the application window
[string]$AppTitle = "Frida - Full Retail ISO Downloads" [string]$AppTitle = "Frida - Full Retail ISO Downloader"
# TODO: Add a -NoHide param
) )
Write-Host Please Wait...
# Custom Assembly Types # Custom Assembly Types
$code = @" $code = @"
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
@@ -39,6 +45,8 @@ $code = @"
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax); internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
[DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons); internal static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
[DllImport("user32.dll")]
public static extern bool ShowWindow(int handle, int state);
// Returns a localized MUI string from the specified DLL // Returns a localized MUI string from the specified DLL
public static string GetMuiString(string dll, uint index) public static string GetMuiString(string dll, uint index)
@@ -70,6 +78,8 @@ $code = @"
Add-Type -MemberDefinition $code -Namespace Gui -UsingNamespace "System.IO", "System.Text", "System.Drawing", "System.Globalization" -ReferencedAssemblies System.Drawing -Name Utils -ErrorAction Stop Add-Type -MemberDefinition $code -Namespace Gui -UsingNamespace "System.IO", "System.Text", "System.Drawing", "System.Globalization" -ReferencedAssemblies System.Drawing -Name Utils -ErrorAction Stop
Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationFramework
# Hide the powershell window: https://stackoverflow.com/a/27992426/1069307
$null = [Gui.Utils]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle, 0)
# Data # Data
$WindowsVersions = @( $WindowsVersions = @(
@@ -116,6 +126,32 @@ $WindowsVersions = @(
) )
) )
# Translated messages. Empty string means same as English
$Translations = @(
@(
"en-US",
"Windows version",
"Release",
"Edition",
"Language"
"Arch"
"Download"
"Confirm"
"Cancel"
),
@(
"fr-FR",
"Version de Windows"
"",
"Édition",
"Langue de produit"
"Arch"
"Télécharger"
"Confirmer"
"Abandonner"
)
)
# Functions # Functions
function Add-Title([string]$Name) function Add-Title([string]$Name)
{ {
@@ -152,6 +188,7 @@ function Send-Message([string]$PipeName, [string]$Message)
[System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8 [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8
$Pipe = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList ".", $PipeName, ([System.IO.Pipes.PipeDirection]::Out), ([System.IO.Pipes.PipeOptions]::None), ([System.Security.Principal.TokenImpersonationLevel]::Impersonation) $Pipe = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList ".", $PipeName, ([System.IO.Pipes.PipeDirection]::Out), ([System.IO.Pipes.PipeOptions]::None), ([System.Security.Principal.TokenImpersonationLevel]::Impersonation)
try { try {
Write-Host Connecting to $PipeName
$Pipe.Connect(1000) $Pipe.Connect(1000)
} catch { } catch {
Write-Host $_.Exception.Message Write-Host $_.Exception.Message
@@ -184,6 +221,27 @@ function ConvertTo-ImageSource
} }
} }
# Translate a message string
function Get-Translation([string]$Text)
{
if (-not $Translations[0].Contains($Text)) {
Write-Host "Error: '$Text' is not a translatable string"
return "(Untranslated)"
}
foreach($Translation in $Translations) {
if ($Translation[0].StartsWith($ShortLocale)) {
for ($i = 1; $i -lt $Translation.Length; $i++) {
if ($Translations[0][$i] -eq $Text) {
if ($Translation[$i]) {
return $Translation[$i]
}
}
}
}
}
return $Text
}
function Exit-App([int]$ExitCode) function Exit-App([int]$ExitCode)
{ {
$script:ExitCode = $ExitCode $script:ExitCode = $ExitCode
@@ -191,13 +249,12 @@ function Exit-App([int]$ExitCode)
} }
# XAML Form # XAML Form
# TODO: Add FlowDirection = "RightToLeft" to <Window> for RTL mode
[xml]$Form = @" [xml]$Form = @"
<Window xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" Height = "162" Width = "380" ResizeMode = "NoResize"> <Window xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" Height = "162" Width = "380" ResizeMode = "NoResize">
<Grid Name = "Grid"> <Grid Name = "Grid">
<TextBlock Name = "WindowsVersionTitle" FontSize = "16" Width="340" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="16,8,0,0" Text="Windows Version"/> <TextBlock Name = "WindowsVersionTitle" FontSize = "16" Width="340" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="16,8,0,0"/>
<ComboBox Name = "WindowsVersion" FontSize = "14" Height = "24" Width = "340" HorizontalAlignment = "Left" VerticalAlignment="Top" Margin = "14,34,0,0" SelectedIndex = "0"/> <ComboBox Name = "WindowsVersion" FontSize = "14" Height = "24" Width = "340" HorizontalAlignment = "Left" VerticalAlignment="Top" Margin = "14,34,0,0" SelectedIndex = "0"/>
<Button Name = "Confirm" FontSize = "16" Content = "Confirm" Height = "26" Width = "160" HorizontalAlignment = "Left" VerticalAlignment = "Top" Margin = "14,78,0,0"/> <Button Name = "Confirm" FontSize = "16" Height = "26" Width = "160" HorizontalAlignment = "Left" VerticalAlignment = "Top" Margin = "14,78,0,0"/>
</Grid> </Grid>
</Window> </Window>
"@ "@
@@ -210,23 +267,34 @@ $SessionId = ""
$Url = "" $Url = ""
$ExitCode = 0 $ExitCode = 0
# Locale handling
if (-not $Locale) {
$Locale = "en-US"
}
$ShortLocale = $Locale
if (-not $Locale.StartsWith("zh") -and -not $Locale.StartsWith("pt")) {
$ShortLocale = $Locale.Substring(0, 2)
}
# Form creation
$XMLReader = New-Object System.Xml.XmlNodeReader $Form $XMLReader = New-Object System.Xml.XmlNodeReader $Form
$XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader) $XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader)
# Set application settings (icon, title, RTL mode)
$XMLForm.Title = $AppTitle $XMLForm.Title = $AppTitle
if ($Icon) { if ($Icon) {
$XMLForm.Icon = $Icon $XMLForm.Icon = $Icon
} else { } else {
$XMLForm.Icon = [Gui.Utils]::ExtractIcon("shell32.dll", -262, $true) | ConvertTo-ImageSource $XMLForm.Icon = [Gui.Utils]::ExtractIcon("shell32.dll", -41, $true) | ConvertTo-ImageSource
} }
if ($Locale.StartsWith("ar-") -or $Locale.StartsWith("fa-") -or $Locale.StartsWith("he-")) { if ($Locale.StartsWith("ar-") -or $Locale.StartsWith("fa-") -or $Locale.StartsWith("he-")) {
$XMLForm.FlowDirection = "RightToLeft" $XMLForm.FlowDirection = "RightToLeft"
} }
$XMLGrid = $XMLForm.FindName("Grid") $XMLGrid = $XMLForm.FindName("Grid")
$Confirm = $XMLForm.FindName("Confirm") $Confirm = $XMLForm.FindName("Confirm")
$Confirm.Content = Get-Translation("Confirm")
# Populate in the Windows Version dropdown # Populate in the Windows Version dropdown
$WindowsVersionTitle = $XMLForm.FindName("WindowsVersionTitle") $WindowsVersionTitle = $XMLForm.FindName("WindowsVersionTitle")
$WindowsVersionTitle.Text = Get-Translation("Windows version")
$WindowsVersion = $XMLForm.FindName("WindowsVersion") $WindowsVersion = $XMLForm.FindName("WindowsVersion")
$array = @() $array = @()
$i = 0 $i = 0
@@ -251,8 +319,11 @@ $Confirm.add_click({
# Force a refresh of the Confirm button so it is actually disabled # Force a refresh of the Confirm button so it is actually disabled
$Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) $Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null)
$url = "https://www.microsoft.com/" + $Locale + "/software-download/windows10ISO/"
Write-Host Querying $url
try { try {
$r = Invoke-WebRequest -SessionVariable "Session" "https://www.microsoft.com/en-us/software-download/windows10ISO/" $r = Invoke-WebRequest -SessionVariable "Session" $url
$script:SessionId = $r.ParsedHtml.IHTMLDocument3_GetElementById("session-id").Value $script:SessionId = $r.ParsedHtml.IHTMLDocument3_GetElementById("session-id").Value
if (-not $SessionId) { if (-not $SessionId) {
throw "Could not read Session ID" throw "Could not read Session ID"
@@ -262,7 +333,7 @@ $Confirm.add_click({
$UserInput = [System.Windows.MessageBox]::Show("Error: " + $_.Exception.Message, "Error", "OK", "Error") $UserInput = [System.Windows.MessageBox]::Show("Error: " + $_.Exception.Message, "Error", "OK", "Error")
Exit-App($Stage) Exit-App($Stage)
} }
$script:WindowsReleaseTitle = Add-Title($WindowsVersion.SelectedValue.Version + " Release") $script:WindowsReleaseTitle = Add-Title(Get-Translation("Release"))
$script:WindowsRelease = Add-Combo $script:WindowsRelease = Add-Combo
$i = 0 $i = 0
@@ -283,7 +354,7 @@ $Confirm.add_click({
2 { # Windows Release selection => Populate Product Edition 2 { # Windows Release selection => Populate Product Edition
$WindowsRelease.IsEnabled = $False $WindowsRelease.IsEnabled = $False
$ProductEditionTitle = Add-Title("Edition") $ProductEditionTitle = Add-Title(Get-Translation("Edition"))
$script:ProductEdition = Add-Combo $script:ProductEdition = Add-Combo
$array = @() $array = @()
@@ -304,11 +375,11 @@ $Confirm.add_click({
$ProductEdition.IsEnabled = $False $ProductEdition.IsEnabled = $False
$Confirm.IsEnabled = $False $Confirm.IsEnabled = $False
$Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) $Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null)
$LanguageTitle = Add-Title("Language") $LanguageTitle = Add-Title(Get-Translation("Language"))
$script:Language = Add-Combo $script:Language = Add-Combo
# Get the Product Edition # Get the Product Edition
$url = "https://www.microsoft.com/en-us/api/controls/contentinclude/html" $url = "https://www.microsoft.com/" + $Locale + "/api/controls/contentinclude/html"
$url += "?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041" $url += "?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041"
$url += "&host=www.microsoft.com" $url += "&host=www.microsoft.com"
$url += "&segments=software-download,windows10ISO" $url += "&segments=software-download,windows10ISO"
@@ -316,6 +387,7 @@ $Confirm.add_click({
$url += "&sessionId=" + $SessionId $url += "&sessionId=" + $SessionId
$url += "&productEditionId=" + $ProductEdition.SelectedValue.Id $url += "&productEditionId=" + $ProductEdition.SelectedValue.Id
$url += "&sdVersion=2" $url += "&sdVersion=2"
Write-Host Querying $url
try { try {
$r = Invoke-WebRequest -WebSession $Session $url $r = Invoke-WebRequest -WebSession $Session $url
@@ -323,7 +395,7 @@ $Confirm.add_click({
foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementByID("product-languages")) { foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementByID("product-languages")) {
$json = $var.value | ConvertFrom-Json; $json = $var.value | ConvertFrom-Json;
if ($json) { if ($json) {
$array += @(New-Object PsObject -Property @{ Language = $json.language; Id = $json.id }) $array += @(New-Object PsObject -Property @{ DisplayLanguage = $var.text; Language = $json.language; Id = $json.id })
} }
} }
if ($array.Length -eq 0) { if ($array.Length -eq 0) {
@@ -335,8 +407,8 @@ $Confirm.add_click({
Exit-App($Stage) Exit-App($Stage)
} }
$Language.ItemsSource = $array $Language.ItemsSource = $array
$Language.DisplayMemberPath = "Language" $Language.DisplayMemberPath = "DisplayLanguage"
# TODO: Select language that matches current MUI settings # TODO: Select the language that matches $Locale
$XMLGrid.AddChild($LanguageTitle) $XMLGrid.AddChild($LanguageTitle)
$XMLGrid.AddChild($Language) $XMLGrid.AddChild($Language)
$Confirm.IsEnabled = $True $Confirm.IsEnabled = $True
@@ -349,7 +421,7 @@ $Confirm.add_click({
$ArchTitle = Add-Title("Architecture") $ArchTitle = Add-Title("Architecture")
$script:Arch = Add-Combo $script:Arch = Add-Combo
$url = "https://www.microsoft.com/en-us/api/controls/contentinclude/html" $url = "https://www.microsoft.com/" + $Locale + "/api/controls/contentinclude/html"
$url += "?pageId=cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b" $url += "?pageId=cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b"
$url += "&host=www.microsoft.com" $url += "&host=www.microsoft.com"
$url += "&segments=software-download,windows10ISO" $url += "&segments=software-download,windows10ISO"
@@ -358,6 +430,7 @@ $Confirm.add_click({
$url += "&skuId=" + $Language.SelectedValue.Id $url += "&skuId=" + $Language.SelectedValue.Id
$url += "&language=" + $Language.SelectedValue.Language $url += "&language=" + $Language.SelectedValue.Language
$url += "&sdVersion=2" $url += "&sdVersion=2"
Write-Host Querying $url
try { try {
$r = Invoke-WebRequest -WebSession $Session $url $r = Invoke-WebRequest -WebSession $Session $url
@@ -434,7 +507,7 @@ if ($Url) {
if ($PipeName) { if ($PipeName) {
Send-Message -PipeName $PipeName -Message $Url Send-Message -PipeName $PipeName -Message $Url
} else { } else {
Write-Host $Url Write-Host Download Link: $Url
Start-Process -FilePath $Url Start-Process -FilePath $Url
} }
} }