Recently I was working on a PowerShell project that involved
internationalization. When researching the best approach, I learned about the
Import-LocalizedData
Cmdlet and it made it incredibly easy to support
internationalization in my scripts.
Before I really get into things, let me clarify a couple similar but distinct definitions (this is mostly for my own benefit, because I always get them mixed up):
There are actually quite a few definitions for these terms, but this is how Microsoft defines them. Since we’re talking about PowerShell, a Microsoft product, these are the definitions I’m going to stick with. So you could say that when you are working with internationalization you globalize your application so that it can be localized into multiple languages.
To demonstrate this process, let’s use a simple example. I’ve got a script
called PSUICultureExample.ps1
that looks like this:
[System.Windows.Forms.MessageBox]::Show(
"This is a small example showing how to localize strings in a PowerShell script.",
"Hello, World!") | Out-Null
…and it produces this message box:
Pretty straightforward, but it needs to be globalized.
To globalize the script, we need to extract the strings that are shown to the
user into an external file. We’ll create a subfolder called
Localized
(you can call it whatever you want) and add a file called
PSUICultureExample.psd1
that looks like this:
ConvertFrom-StringData @"
MessageTitle = Hello, World!
MessageBody = This is a small example showing how to localize strings in a PowerShell script.
"@
This file uses the ConvertFrom-StringData
cmdlet and a
here-string to
create a hashtable containing the strings we need as key/value pairs. You can
write any code you’d like that returns a hashtable, I just happen to think this
method is very clean to work with.
The next thing we have to do is load this data into our actual script and use the hashtable data instead of the hard-coded strings. To import the data, we’ll use this command:
$s = Import-LocalizedData -BaseDirectory (Join-Path -Path $PSScriptRoot -ChildPath Localized)
The Import-Localizeddata
Cmdlet, by default, will look in the same folder as
the script for a psd1
file with the same name. We already named our file
PSUICultureExample.psd1
, so we’re alright there, but the file isn’t in the
same folder as our script so we need to specify that path using the
-BaseDirectory
parameter. You can use relative paths in this parameter, but
they are relative to your PowerShell session’s current working directory, not
relative to the script file’s directory. We really should use the full path to
be sure we will always point to the right place. A quick Join-Path
with the
automatic variable $PSScriptRoot
gives us the full path to the Localized
folder we created.
(If for some reason you’re still in PowerShell 2.0, you won’t have
$PSScriptRoot
. Try this
instead.)
Now that we have our string data loaded, we just have to swap out the hard-coded
strings and our PSUICultureExample.ps1
file looks like this:
$s = Import-LocalizedData -BaseDirectory (Join-Path -Path $PSScriptRoot -ChildPath Localized)
[System.Windows.Forms.MessageBox]::Show($s.MessageBody, $s.MessageTitle) | Out-Null
That’s all there is to globalizing our script!
The next step, localizing the script, is very easy because of the way we
implemented our globalization using Import-LocalizedData
. This Cmdlet uses an
automatic variable called $PSUICulture
(Now you see why I named the script
that, right?) to determine what language it should try to display to the user
and it will attempt to locate the correct version of our psd1 file by looking in
subfolders named for the appropriate locale. For example, let’s say I install
the Spanish Language Pack and change all of my settings to Spanish
(Spain). the $PSUICulture
automatic variable will contain es-ES
instead
of en-US
like it had previously. So what happens when I run my script now?
Yep, everything is still in English. Why? Because I haven’t properly localized
the script for Spanish yet. To localize for Spanish, we need to create a
subfolder in the Localized
folder called es-ES
, and make a copy of the
PSUICulture.psd1
file there that contains the same hashtable keys but Spanish
values instead of English ones. I took Spanish in high school, but I’m going to
let Google Translate handle this one:
ConvertFrom-StringData @"
MessageTitle = ¡Hola mundo!
MessageBody = Este es un pequeño ejemplo que muestra cómo localizar cadenas en un script de PowerShell.
"@
…and now when I run it, it looks like this:
¡Bueno! Now everything works for our users in Spain. Our two-line script will automatically display Spanish text for users that have Windows configured to display the UI in Spanish.
This is what the folder structure of the full example looks like.
Globalization and Localization are both easy in PowerShell thanks to
Import-LocalizedData
. It’s a powerful Cmdlet that takes a lot of the logic of
picking a language and loading the correct string data out of your hands. If
you’re building a script, even if you’re not planning to localize it, I would
recommend globalizing your string data. It’s a good practice to get into, and
you never know if you might need to localize down the line if the script becomes
popular in the community.
There are two last points I want to bring up. The first is the reason why the
English strings file isn’t in an en-US
folder. This would have worked just
fine, but by leaving a file in the base folder, we are giving
Import-LocalizedData
a fallback to use if it can’t find a matching psd1
file
for the UI culture specified in $PSUICulture
.
The second thing I want to add is that Import-LocalizedData
will not merge a
language-specific file with the fallback file. If you define five values in the
psd1
file in your base directory but only four of those in the es-ES
version
of the file, your Spanish users will not see the English value for that fifth
value, it will simply be null.
I’ve posted the full example on a GitHub repository so you can easily try it yourself. This is also a great opportunity to become more familiar with using Git and GitHub. My repository only has the English and Spanish translations, I would love to receive some pull requests to add more languages!
Update: I originally started writing about Import-LocalizedData
so I could
share an issue I ran into while working with it. I’ve written that story in a
follow-up post.
Adam Platt is a technologist with more than a decade of experience across the full stack. His passion for technology and penchant for rendering complex technical ideas into simple terms have made him an in-demand speaker. His resume includes BriForum, the PowerShell Summit, teaching engagements and more.
He is one of the 10 types of people who understand binary and he can solve a Rubik’s Cube.
Adam Platt is a technologist with more than a decade of experience across the full stack. His passion for technology and penchant for rendering complex technical ideas into simple terms have made him an in-demand speaker. His resume includes BriForum, the PowerShell Summit, teaching engagements and more.
He is one of the 10 types of people who understand binary and he can solve a Rubik’s Cube.