Windows PowerShell 2.0 String Localization

One of the lesser known features in PowerShell 2.0 is that it supports string localization and pretty simply I might add.  Now for most developers and admins script localization probably isn’t going to be something you’ll worry about.  However if you are providing PowerShell based solutions to an international audience this feature will come in very handy in terms of broadening your reach.

I believe the driving force behind this feature’s inclusion in PowerShell is that a critical component of Windows 7 – the Windows Troubleshooting Platform and associated troubleshooting packs – use PowerShell scripts extensively.  Obviously Windows is localized to the extreme so it stands to reason that the troubleshooting scripts, which can interact with the end user via text prompts, were required to be localized.  Enough context.  Let’s look at how localization works in PowerShell 2.0.

First, the feature in PowerShell is referred to as “Data Sections” and is covered by the help topics:

  • about_data_sections
  • about_script_internationalization

Let’s start by creating our string table file.  This is created as a .psd1 (data) file.  In this case, we will call it messages.psd1 and its contents are simply:

# Contents of .\messages.psd1
ConvertFrom-StringData @' Hello_F1=Hello {0} Goodbye=Goodbye '@

This is a bit odd looking for a “data” file but the gist is that the ConvertFrom-StringData cmdlet returns a ordinary hashtable.  The string that ConvertFrom-StringData operates on has to be in a special format which is essentially one key/value pair per line where the key and value are separated by an “=”.  The “key” is how you will reference the “value” (actual localized string) from your script.  FWIW you could just have the psd1 file return a hashtable directly but then you have to quote each value.  So the format above is a bit cleaner and more like a traditional string table file.

Now at this point you could and might be tempted to do something like this in your script:

$msgs = .\messages.psd1

But there is one tiny little problem – .psd1 files are not executable like .ps1 or .psm1 files are.  Hey, that’s probably why they are called “data” files.  🙂  Obviously PowerShell must provide a way to load these files and that mechanism is the Import-LocalizedData cmdlet.  It takes the path to a .psd1 file, loads it and then assigns the resulting hashtable to a variable name provided to the cmdlet.  Let’s see an example of this:

# Contents of test.ps1
Import-LocalizedData -BindingVariable msgs -FileName Messages.psd1 $msgs.Hello_F1 -f "John" $msgs.Goodbye

The output of this script is:

PS> .\test.ps1
Hello John
Goodbye

You may be thinking that this seems like a pretty simple task, not worthy of requiring a dedicated cmdlet.  However there is one very important piece of “non-trivial” functionality that the Import-LocalizedData cmdlet provides and that is it searches through various culture specific sub directories looking for a matching .psd1 file for the user’s culture.  For those familiar with .NET development this is very similar to how the .NET binder looks through application’s base sub dirs for the appropriate satellite assembly for the user’s culture.  Let’s see an example of how this works.

First, let’s create a German version of our string resources and put the file in a sub dir titled de-DE.  The name of this sub dir is important.  The name reflects the <language>-<region> that the localized strings target.  Here are the contents of .\de-DE\messages.psd1:

# Contents of .\de-DE\messages.psd1
ConvertFrom-StringData @' Hello_F1=Wie Geht {0} Goodbye=Auf Wiedersehen '@

At this point our dir structure looks like this:

.\test.ps1
.\messages.psd1 (English – fallback)
.\de-DE\messages.psd1

Now I need to introduce a somewhat orthogonal but very handy function called Using-Culture that the PowerShell team blogged a long time ago.  I have updated it for PowerShell 2.0 and I use it to effectively simulate running PowerShell scripts in different cultures.

function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"),
[ScriptBlock]$script=(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"))
{
$OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
$OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture
try {
[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture
Invoke-Command $script
}
finally {
[System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture
}
}

Now let’s try running out test.ps1 script under the German language/region:

PS> Using-Culture de-DE { .\test.ps1 }
Wie Geht John
Auf Wiedersehen

Now that’s what I’m talking about!  Script localization without requiring an advanced degree.  Now, you might ask – what happens when somebody run’s in a culture that I have not localized for?  Let’s see:

PS> Using-Culture fr-FR { .\test.ps1 }
Hello John
Goodbye

When I listed the dir contents above I mentioned that the top-level messages.psd1 was the “fallback”.  Essentially if PowerShell can’t find the specified data file for the current culture it will “fallback” to the top-level data file or .\messages.psd1 in this case (which is English).  This may very well be a feature you never use but if you desire to reach a broad audience with your PowerShell 2.0 modules, consider pulling out your strings into a data file even if you only provide a data file for your native language.  This gives others a much better chance of localizing your module into other languages for you!

Advertisements
This entry was posted in PowerShell. Bookmark the permalink.

4 Responses to Windows PowerShell 2.0 String Localization

  1. fridojet says:

    That’s a very strange German translation which wouldn’t make any sense to any German speaker. Here’s a much nicer version:

    ==Southern German==
    # Contents of .\de-DE\messages.psd1
    ConvertFrom-StringData @’
    Hello_F1=Servus {0}
    Goodbye=Servus!
    ‘@

    ==Northern German==
    # Contents of .\de-DE\messages.psd1
    ConvertFrom-StringData @’
    Hello_F1=Moin {0}
    Goodbye=Tschüss
    ‘@

    lg.

  2. fridojet says:

    PS: “Auf Wiedersehen” would be a correct leave-taking, but you would never say “Auf Wiedersehen” to someone you know very well. But you call your dialog partner by his first name (John), so you have to use a more personal leave-taking.

    lg,

  3. Markus Egger says:

    As a thank you I’ve beefed up the Using-Culture stuff a bit so it’s more 2.0ish 🙂
    function Using-Culture
    {
    Using-Culture fr-FR { .\test.ps1 }

    Runs the script test.ps1 under French language settings.
    #>

    [CmdletBinding()]

    Param([Parameter(Mandatory = $true, HelpMessage = ‘The culture (language) to run the script in.’)]
    [ValidateNotNull()]
    [System.Globalization.CultureInfo]
    $Culture,
    [Parameter(Mandatory = $true, HelpMessage = ‘The scriptblock or wrapped script to run.’)]
    [ValidateNotNull()]
    [ScriptBlock]
    $Script
    )

    $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
    $OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture

    try
    {
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
    [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture

    Invoke-Command $script
    }
    finally
    {
    [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture
    [System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture
    }
    }

  4. Markus Egger says:

    Since the formatting/special characters were screwed, see http://poshcode.org/4120

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s